diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java index a9c7b8637..92ac358a5 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java @@ -163,10 +163,22 @@ class PeerConnectionOut implements Runnable removeMessage(Message.PIECE); // XXX - Should also register overhead... - if (m.type == Message.PIECE) - state.uploaded(m.len); + // Don't let other clients requesting big chunks get an advantage + // when we are seeding; + // only count the rest of the upload after sendMessage(). + int remainder = 0; + if (m.type == Message.PIECE) { + if (m.len <= PeerState.PARTSIZE) { + state.uploaded(m.len); + } else { + state.uploaded(PeerState.PARTSIZE); + remainder = m.len - PeerState.PARTSIZE; + } + } m.sendMessage(dout); + if (remainder > 0) + state.uploaded(remainder); m = null; } } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 5f215dcf6..7f6759a5f 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -203,7 +203,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable if (size <= 0) { if (_log.shouldLog(Log.INFO)) _log.info("Proxy list is empty - no outproxy available"); - l.log("Proxy list is emtpy - no outproxy available"); + l.log("Proxy list is empty - no outproxy available"); return null; } int index = I2PAppContext.getGlobalContext().random().nextInt(size); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java index 2720b6fd4..029228c72 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPUnwrapper.java @@ -50,7 +50,7 @@ public class SOCKSUDPUnwrapper implements Source, Sink { int headerlen = h.getBytes().length; byte unwrapped[] = new byte[data.length - headerlen]; - System.arraycopy(unwrapped, 0, data, headerlen, unwrapped.length); + System.arraycopy(data, headerlen, unwrapped, 0, unwrapped.length); this.sink.send(dest, unwrapped); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java index 4ec836157..eab156841 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPWrapper.java @@ -39,8 +39,8 @@ public class SOCKSUDPWrapper implements Source, Sink { byte[] header = h.getBytes(); byte wrapped[] = new byte[header.length + data.length]; - System.arraycopy(wrapped, 0, header, 0, header.length); - System.arraycopy(wrapped, header.length, data, 0, data.length); + System.arraycopy(header, 0, wrapped, 0, header.length); + System.arraycopy(data, 0, wrapped, header.length, data.length); this.sink.send(from, wrapped); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java index 0da8bdc24..896b8b200 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigNetHandler.java @@ -298,6 +298,9 @@ public class ConfigNetHandler extends FormHandler { _context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART); } + private static final int DEF_BURST_PCT = 10; + private static final int DEF_BURST_TIME = 20; + private void updateRates() { boolean updated = false; @@ -310,14 +313,27 @@ public class ConfigNetHandler extends FormHandler { } } + // Since burst is now hidden in the gui, set burst to +10% for 20 seconds if ( (_inboundRate != null) && (_inboundRate.length() > 0) && !_inboundRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_INBOUND_BANDWIDTH))) { _context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, _inboundRate); + try { + int rate = Integer.parseInt(_inboundRate) * (100 + DEF_BURST_PCT) / 100; + int kb = DEF_BURST_TIME * rate; + _context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, "" + rate); + _context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH_PEAK, "" + kb); + } catch (NumberFormatException nfe) {} updated = true; } if ( (_outboundRate != null) && (_outboundRate.length() > 0) && !_outboundRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_OUTBOUND_BANDWIDTH))) { _context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, _outboundRate); + try { + int rate = Integer.parseInt(_outboundRate) * (100 + DEF_BURST_PCT) / 100; + int kb = DEF_BURST_TIME * rate; + _context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, "" + rate); + _context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH_PEAK, "" + kb); + } catch (NumberFormatException nfe) {} updated = true; } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java index 488275a23..df7dc6697 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NavHelper.java @@ -7,7 +7,7 @@ import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; public class NavHelper { - private static Map _apps = new ConcurrentHashMap(); + private static Map _apps = new ConcurrentHashMap(4); /** * To register a new client application so that it shows up on the router @@ -28,7 +28,9 @@ public class NavHelper { * Translated string is loaded by PluginStarter */ public static String getClientAppLinks(I2PAppContext ctx) { - StringBuilder buf = new StringBuilder(1024); + if (_apps.isEmpty()) + return ""; + StringBuilder buf = new StringBuilder(256); for (Iterator iter = _apps.keySet().iterator(); iter.hasNext(); ) { String name = iter.next(); String path = _apps.get(name); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java index 715d92c74..3497bc867 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -282,7 +282,11 @@ public class NetDbRenderer { } for (Iterator iter = info.getAddresses().iterator(); iter.hasNext(); ) { RouterAddress addr = (RouterAddress)iter.next(); - buf.append("").append(DataHelper.stripHTML(addr.getTransportStyle())).append(": "); + String style = addr.getTransportStyle(); + buf.append("").append(DataHelper.stripHTML(style)).append(": "); + int cost = addr.getCost(); + if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10))) + buf.append('[').append("cost").append('=').append("" + cost).append("] "); for (Iterator optIter = addr.getOptions().keySet().iterator(); optIter.hasNext(); ) { String name = (String)optIter.next(); String val = addr.getOptions().getProperty(name); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index 030f7670b..1e14b8917 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -180,7 +180,7 @@ public class PluginStarter implements Runnable { * @return true on success * @throws just about anything, caller would be wise to catch Throwable */ - static boolean stopPlugin(RouterContext ctx, String appName) throws IOException { + static boolean stopPlugin(RouterContext ctx, String appName) throws Exception { Log log = ctx.logManager().getLog(PluginStarter.class); File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName); if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) { @@ -228,7 +228,7 @@ public class PluginStarter implements Runnable { } /** @return true on success - caller should call stopPlugin() first */ - static boolean deletePlugin(RouterContext ctx, String appName) throws IOException { + static boolean deletePlugin(RouterContext ctx, String appName) throws Exception { Log log = ctx.logManager().getLog(PluginStarter.class); File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName); if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) { @@ -348,8 +348,12 @@ public class PluginStarter implements Runnable { } catch (IOException ioe) {} } - /** @param action "start" or "stop" or "uninstall" */ - private static void runClientApps(RouterContext ctx, File pluginDir, List apps, String action) { + /** + * @param action "start" or "stop" or "uninstall" + * @throws just about anything if an app has a delay less than zero, caller would be wise to catch Throwable + * If no apps have a delay less than zero, it shouldn't throw anything + */ + private static void runClientApps(RouterContext ctx, File pluginDir, List apps, String action) throws Exception { Log log = ctx.logManager().getLog(PluginStarter.class); for(ClientAppConfig app : apps) { if (action.equals("start") && app.disabled) @@ -388,10 +392,18 @@ public class PluginStarter implements Runnable { } addToClasspath(cp, app.clientName, log); } - if (app.delay == 0 || !action.equals("start")) { + + if (app.delay < 0 && action.equals("start")) { + // this will throw exceptions + LoadClientAppsJob.runClientInline(app.className, app.clientName, argVal, log); + } else if (app.delay == 0 || !action.equals("start")) { + // quick check, will throw ClassNotFoundException on error + LoadClientAppsJob.testClient(app.className); // run this guy now LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log); } else { + // quick check, will throw ClassNotFoundException on error + LoadClientAppsJob.testClient(app.className); // wait before firing it up ctx.jobQueue().addJob(new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay)); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java index 1f1d3de26..f411538a3 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/UpdateHandler.java @@ -205,13 +205,14 @@ public class UpdateHandler { } else { _log.log(Log.CRIT, "Update was VERIFIED, will be installed at next restart"); StringBuilder buf = new StringBuilder(64); - buf.append("").append(_("Update downloaded")).append("
"); + buf.append("").append(_("Update downloaded")).append("
"); if (System.getProperty("wrapper.version") != null) buf.append(_("Click Restart to install")); else buf.append(_("Click Shutdown and restart to install")); if (up.newVersion() != null) buf.append(' ').append(_("Version {0}", up.newVersion())); + buf.append("
"); updateStatus(buf.toString()); } } else { diff --git a/apps/routerconsole/jsp/logs.jsp b/apps/routerconsole/jsp/logs.jsp index 5aa0150fb..537ec8d80 100644 --- a/apps/routerconsole/jsp/logs.jsp +++ b/apps/routerconsole/jsp/logs.jsp @@ -10,7 +10,8 @@

<%=intl._("I2P Router Logs")%>

<%=intl._("I2P Version & Running Environment")%>

- <%=intl._("Please include this information in bug reports")%>: +

<%=intl._("Please report bugs on trac.i2p2.i2p.")%> +

<%=intl._("Please include this information in bug reports")%>:

I2P version:
Java version: <%=System.getProperty("java.vendor")%> <%=System.getProperty("java.version")%>
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java index d9dd9b488..f825f25a7 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java @@ -6,6 +6,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicLong; import net.i2p.I2PAppContext; import net.i2p.client.I2PSession; @@ -29,7 +30,7 @@ public class Connection { private long _sendStreamId; private long _receiveStreamId; private long _lastSendTime; - private long _lastSendId; + private AtomicLong _lastSendId; private boolean _resetReceived; private boolean _resetSent; private long _resetSentOn; @@ -49,7 +50,7 @@ public class Connection { private boolean _isInbound; private boolean _updatedShareOpts; /** Packet ID (Long) to PacketLocal for sent but unacked packets */ - private final Map _outboundPackets; + private final Map _outboundPackets; private PacketQueue _outboundQueue; private ConnectionPacketHandler _handler; private ConnectionOptions _options; @@ -102,7 +103,7 @@ public class Connection { _options = (opts != null ? opts : new ConnectionOptions()); _outputStream.setWriteTimeout((int)_options.getWriteTimeout()); _inputStream.setReadTimeout((int)_options.getReadTimeout()); - _lastSendId = -1; + _lastSendId = new AtomicLong(-1); _nextSendTime = -1; _ackedPackets = 0; _createdOn = _context.clock().now(); @@ -137,9 +138,7 @@ public class Connection { } public long getNextOutboundPacketNum() { - synchronized (this) { - return ++_lastSendId; - } + return _lastSendId.incrementAndGet(); } void closeReceived() { @@ -175,7 +174,7 @@ public class Connection { return false; started = true; if ( (_outboundPackets.size() >= _options.getWindowSize()) || (_activeResends > 0) || - (_lastSendId - _highestAckedThrough > _options.getWindowSize()) ) { + (_lastSendId.get() - _highestAckedThrough > _options.getWindowSize()) ) { if (timeoutMs > 0) { if (timeLeft <= 0) { if (_log.shouldLog(Log.INFO)) @@ -211,10 +210,10 @@ public class Connection { void ackImmediately() { PacketLocal packet = null; synchronized (_outboundPackets) { - if (_outboundPackets.size() > 0) { + if (!_outboundPackets.isEmpty()) { // ordered, so pick the lowest to retransmit - Iterator iter = _outboundPackets.values().iterator(); - packet = (PacketLocal)iter.next(); + Iterator iter = _outboundPackets.values().iterator(); + packet = iter.next(); //iter.remove(); } } @@ -403,10 +402,10 @@ public class Connection { } } - List acked = null; + List acked = null; synchronized (_outboundPackets) { - for (Iterator iter = _outboundPackets.keySet().iterator(); iter.hasNext(); ) { - Long id = (Long)iter.next(); + for (Iterator iter = _outboundPackets.keySet().iterator(); iter.hasNext(); ) { + Long id = iter.next(); if (id.longValue() <= ackThrough) { boolean nacked = false; if (nacks != null) { @@ -414,7 +413,7 @@ public class Connection { for (int i = 0; i < nacks.length; i++) { if (nacks[i] == id.longValue()) { nacked = true; - PacketLocal nackedPacket = (PacketLocal)_outboundPackets.get(id); + PacketLocal nackedPacket = _outboundPackets.get(id); nackedPacket.incrementNACKs(); break; // NACKed } @@ -423,7 +422,7 @@ public class Connection { if (!nacked) { // aka ACKed if (acked == null) acked = new ArrayList(1); - PacketLocal ackedPacket = (PacketLocal)_outboundPackets.get(id); + PacketLocal ackedPacket = _outboundPackets.get(id); ackedPacket.ackReceived(); acked.add(ackedPacket); } @@ -433,7 +432,7 @@ public class Connection { } if (acked != null) { for (int i = 0; i < acked.size(); i++) { - PacketLocal p = (PacketLocal)acked.get(i); + PacketLocal p = acked.get(i); _outboundPackets.remove(new Long(p.getSequenceNum())); _ackedPackets++; if (p.getNumSends() > 1) { @@ -443,7 +442,7 @@ public class Connection { } } } - if ( (_outboundPackets.size() <= 0) && (_activeResends != 0) ) { + if ( (_outboundPackets.isEmpty()) && (_activeResends != 0) ) { if (_log.shouldLog(Log.INFO)) _log.info("All outbound packets acked, clearing " + _activeResends); _activeResends = 0; @@ -570,8 +569,8 @@ public class Connection { private void killOutstandingPackets() { //boolean tagsCancelled = false; synchronized (_outboundPackets) { - for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) { - PacketLocal pl = (PacketLocal)iter.next(); + for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) { + PacketLocal pl = iter.next(); //if ( (pl.getTagsSent() != null) && (pl.getTagsSent().size() > 0) ) // tagsCancelled = true; pl.cancelled(); @@ -652,11 +651,11 @@ public class Connection { /** What was the last packet Id sent to the peer? * @return The last sent packet ID */ - public long getLastSendId() { return _lastSendId; } + public long getLastSendId() { return _lastSendId.get(); } /** Set the packet Id that was sent to a peer. * @param id The packet ID */ - public void setLastSendId(long id) { _lastSendId = id; } + public void setLastSendId(long id) { _lastSendId.set(id); } /** * Retrieve the current ConnectionOptions. @@ -783,7 +782,7 @@ public class Connection { if (_ackSinceCongestion) { _lastCongestionSeenAt = _options.getWindowSize(); _lastCongestionTime = _context.clock().now(); - _lastCongestionHighestUnacked = _lastSendId; + _lastCongestionHighestUnacked = _lastSendId.get(); _ackSinceCongestion = false; } } @@ -1022,7 +1021,7 @@ public class Connection { } if (getCloseReceivedOn() > 0) buf.append(" close received ").append(DataHelper.formatDuration(_context.clock().now() - getCloseReceivedOn())).append(" ago"); - buf.append(" sent: ").append(1 + _lastSendId); + buf.append(" sent: ").append(1 + _lastSendId.get()); if (_inputStream != null) buf.append(" rcvd: ").append(1 + _inputStream.getHighestBlockId() - missing); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java index af44c41f2..685b87b06 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java @@ -1,10 +1,10 @@ package net.i2p.client.streaming; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.I2PAppContext; import net.i2p.I2PException; @@ -32,14 +32,13 @@ public class ConnectionManager { private ConnectionPacketHandler _conPacketHandler; private TCBShare _tcbShare; /** Inbound stream ID (Long) to Connection map */ - private Map _connectionByInboundId; + private ConcurrentHashMap _connectionByInboundId; /** Ping ID (Long) to PingRequest */ - private final Map _pendingPings; + private final Map _pendingPings; private boolean _allowIncoming; private int _maxConcurrentStreams; private ConnectionOptions _defaultOptions; private volatile int _numWaiting; - private final Object _connectionLock; private long SoTimeout; public ConnectionManager(I2PAppContext context, I2PSession session, int maxConcurrent, ConnectionOptions defaultOptions) { @@ -48,9 +47,8 @@ public class ConnectionManager { _maxConcurrentStreams = maxConcurrent; _defaultOptions = defaultOptions; _log = _context.logManager().getLog(ConnectionManager.class); - _connectionByInboundId = new HashMap(32); - _pendingPings = new HashMap(4); - _connectionLock = new Object(); + _connectionByInboundId = new ConcurrentHashMap(32); + _pendingPings = new ConcurrentHashMap(4); _messageHandler = new MessageHandler(_context, this); _packetHandler = new PacketHandler(_context, this); _connectionHandler = new ConnectionHandler(_context, this); @@ -77,22 +75,17 @@ public class ConnectionManager { } Connection getConnectionByInboundId(long id) { - synchronized (_connectionLock) { - return (Connection)_connectionByInboundId.get(new Long(id)); - } + return _connectionByInboundId.get(Long.valueOf(id)); } /** * not guaranteed to be unique, but in case we receive more than one packet * on an inbound connection that we havent ack'ed yet... */ Connection getConnectionByOutboundId(long id) { - synchronized (_connectionLock) { - for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) { - Connection con = (Connection)iter.next(); + for (Connection con : _connectionByInboundId.values()) { if (DataHelper.eq(con.getSendStreamId(), id)) return con; } - } return null; } @@ -135,27 +128,26 @@ public class ConnectionManager { boolean reject = false; int active = 0; int total = 0; - synchronized (_connectionLock) { - total = _connectionByInboundId.size(); - for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) { - if ( ((Connection)iter.next()).getIsConnected() ) - active++; - } + + // just for the stat + //total = _connectionByInboundId.size(); + //for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) { + // if ( ((Connection)iter.next()).getIsConnected() ) + // active++; + //} if (locked_tooManyStreams()) { reject = true; } else { while (true) { - Connection oldCon = (Connection)_connectionByInboundId.put(new Long(receiveId), con); + Connection oldCon = _connectionByInboundId.putIfAbsent(Long.valueOf(receiveId), con); if (oldCon == null) { break; } else { - _connectionByInboundId.put(new Long(receiveId), oldCon); // receiveId already taken, try another receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1; } } } - } _context.statManager().addRateData("stream.receiveActive", active, total); @@ -179,9 +171,7 @@ public class ConnectionManager { try { con.getPacketHandler().receivePacket(synPacket, con); } catch (I2PException ie) { - synchronized (_connectionLock) { - _connectionByInboundId.remove(new Long(receiveId)); - } + _connectionByInboundId.remove(Long.valueOf(receiveId)); return null; } @@ -215,8 +205,7 @@ public class ConnectionManager { _numWaiting--; return null; } - boolean reject = false; - synchronized (_connectionLock) { + if (locked_tooManyStreams()) { // allow a full buffer of pending/waiting streams if (_numWaiting > _maxConcurrentStreams) { @@ -227,27 +216,30 @@ public class ConnectionManager { _numWaiting--; return null; } - + // no remaining streams, lets wait a bit - try { _connectionLock.wait(remaining); } catch (InterruptedException ie) {} + // got rid of the lock, so just sleep (fixme?) + // try { _connectionLock.wait(remaining); } catch (InterruptedException ie) {} + try { Thread.sleep(remaining/4); } catch (InterruptedException ie) {} } else { con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, opts); con.setRemotePeer(peer); - while (_connectionByInboundId.containsKey(new Long(receiveId))) { + while (_connectionByInboundId.containsKey(Long.valueOf(receiveId))) { receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1; } - _connectionByInboundId.put(new Long(receiveId), con); + _connectionByInboundId.put(Long.valueOf(receiveId), con); break; // stop looping as a psuedo-wait } - } + } // ok we're in... con.setReceiveStreamId(receiveId); con.eventOccurred(); - _log.debug("Connect() conDelay = " + opts.getConnectDelay()); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Connect() conDelay = " + opts.getConnectDelay()); if (opts.getConnectDelay() <= 0) { con.waitForConnect(); } @@ -258,12 +250,15 @@ public class ConnectionManager { return con; } + /** + * Doesn't need to be locked any more + * @return too many + */ private boolean locked_tooManyStreams() { if (_maxConcurrentStreams <= 0) return false; if (_connectionByInboundId.size() < _maxConcurrentStreams) return false; int active = 0; - for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) { - Connection con = (Connection)iter.next(); + for (Connection con : _connectionByInboundId.values()) { if (con.getIsConnected()) active++; } @@ -293,13 +288,10 @@ public class ConnectionManager { * */ public void disconnectAllHard() { - synchronized (_connectionLock) { - for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) { - Connection con = (Connection)iter.next(); - con.disconnect(false, false); - } - _connectionByInboundId.clear(); - _connectionLock.notifyAll(); + for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) { + Connection con = iter.next(); + con.disconnect(false, false); + iter.remove(); } _tcbShare.stop(); } @@ -310,17 +302,15 @@ public class ConnectionManager { * @param con Connection to drop. */ public void removeConnection(Connection con) { - boolean removed = false; - synchronized (_connectionLock) { - Object o = _connectionByInboundId.remove(new Long(con.getReceiveStreamId())); - removed = (o == con); + + Object o = _connectionByInboundId.remove(Long.valueOf(con.getReceiveStreamId())); + boolean removed = (o == con); if (_log.shouldLog(Log.DEBUG)) _log.debug("Connection removed? " + removed + " remaining: " + _connectionByInboundId.size() + ": " + con); if (!removed && _log.shouldLog(Log.DEBUG)) _log.debug("Failed to remove " + con +"\n" + _connectionByInboundId.values()); - _connectionLock.notifyAll(); - } + if (removed) { _context.statManager().addRateData("stream.con.lifetimeMessagesSent", 1+con.getLastSendId(), con.getLifetime()); MessageInputStream stream = con.getInputStream(); @@ -344,10 +334,8 @@ public class ConnectionManager { /** return a set of Connection objects * @return set of Connection objects */ - public Set listConnections() { - synchronized (_connectionLock) { + public Set listConnections() { return new HashSet(_connectionByInboundId.values()); - } } /** blocking */ @@ -368,7 +356,7 @@ public class ConnectionManager { } public boolean ping(Destination peer, long timeoutMs, boolean blocking, PingNotifier notifier) { - Long id = new Long(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1); + Long id = Long.valueOf(_context.random().nextLong(Packet.MAX_STREAM_ID-1)+1); PacketLocal packet = new PacketLocal(_context, peer); packet.setSendStreamId(id.longValue()); packet.setFlag(Packet.FLAG_ECHO); @@ -381,9 +369,7 @@ public class ConnectionManager { PingRequest req = new PingRequest(peer, packet, notifier); - synchronized (_pendingPings) { - _pendingPings.put(id, req); - } + _pendingPings.put(id, req); _outboundQueue.enqueue(packet); packet.releasePayload(); @@ -393,10 +379,7 @@ public class ConnectionManager { if (!req.pongReceived()) try { req.wait(timeoutMs); } catch (InterruptedException ie) {} } - - synchronized (_pendingPings) { - _pendingPings.remove(id); - } + _pendingPings.remove(id); } else { SimpleTimer.getInstance().addEvent(new PingFailed(id, notifier), timeoutMs); } @@ -418,13 +401,8 @@ public class ConnectionManager { } public void timeReached() { - boolean removed = false; - synchronized (_pendingPings) { - Object o = _pendingPings.remove(_id); - if (o != null) - removed = true; - } - if (removed) { + PingRequest pr = _pendingPings.remove(_id); + if (pr != null) { if (_notifier != null) _notifier.pingComplete(false); if (_log.shouldLog(Log.INFO)) @@ -433,7 +411,7 @@ public class ConnectionManager { } } - private class PingRequest { + private static class PingRequest { private boolean _ponged; private Destination _peer; private PacketLocal _packet; @@ -445,7 +423,8 @@ public class ConnectionManager { _notifier = notifier; } public void pong() { - _log.debug("Ping successful"); + // static, no log + //_log.debug("Ping successful"); //_context.sessionKeyManager().tagsDelivered(_peer.getPublicKey(), _packet.getKeyUsed(), _packet.getTagsSent()); synchronized (ConnectionManager.PingRequest.this) { _ponged = true; @@ -458,10 +437,7 @@ public class ConnectionManager { } void receivePong(long pingId) { - PingRequest req = null; - synchronized (_pendingPings) { - req = (PingRequest)_pendingPings.remove(new Long(pingId)); - } + PingRequest req = _pendingPings.remove(Long.valueOf(pingId)); if (req != null) req.pong(); } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java index 98165cf7d..cfd208c01 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java @@ -1,13 +1,14 @@ package net.i2p.client.streaming; -import java.util.ArrayList; -import java.util.List; +import java.util.Iterator; +import java.util.Set; import net.i2p.I2PAppContext; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; import net.i2p.client.I2PSessionListener; import net.i2p.util.Log; +import net.i2p.util.ConcurrentHashSet; /** * Receive raw information from the I2PSession and turn it into @@ -18,12 +19,12 @@ public class MessageHandler implements I2PSessionListener { private ConnectionManager _manager; private I2PAppContext _context; private Log _log; - private final List _listeners; + private final Set _listeners; public MessageHandler(I2PAppContext ctx, ConnectionManager mgr) { _manager = mgr; _context = ctx; - _listeners = new ArrayList(1); + _listeners = new ConcurrentHashSet(1); _log = ctx.logManager().getLog(MessageHandler.class); _context.statManager().createRateStat("stream.packetReceiveFailure", "When do we fail to decrypt or otherwise receive a packet sent to us?", "Stream", new long[] { 60*60*1000, 24*60*60*1000 }); } @@ -77,14 +78,10 @@ public class MessageHandler implements I2PSessionListener { _log.warn("I2PSession disconnected"); _manager.disconnectAllHard(); - List listeners = null; - synchronized (_listeners) { - listeners = new ArrayList(_listeners); - _listeners.clear(); - } - for (int i = 0; i < listeners.size(); i++) { - I2PSocketManager.DisconnectListener lsnr = (I2PSocketManager.DisconnectListener)listeners.get(i); + for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) { + I2PSocketManager.DisconnectListener lsnr = iter.next(); lsnr.sessionDisconnected(); + iter.remove(); } } @@ -104,13 +101,9 @@ public class MessageHandler implements I2PSessionListener { } public void addDisconnectListener(I2PSocketManager.DisconnectListener lsnr) { - synchronized (_listeners) { _listeners.add(lsnr); - } } public void removeDisconnectListener(I2PSocketManager.DisconnectListener lsnr) { - synchronized (_listeners) { _listeners.remove(lsnr); - } } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java b/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java index de0a74228..1a7f0afbe 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/MessageOutputStream.java @@ -222,8 +222,8 @@ public class MessageOutputStream extends OutputStream { // 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.INFO)) - _log.info("Enqueueing the flusher for " + _passiveFlushDelay + "ms out"); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Enqueueing the flusher for " + _passiveFlushDelay + "ms out"); } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("NOT enqueing the flusher"); diff --git a/apps/streaming/java/src/net/i2p/client/streaming/SchedulerChooser.java b/apps/streaming/java/src/net/i2p/client/streaming/SchedulerChooser.java index be62f1766..2c8f5df01 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/SchedulerChooser.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/SchedulerChooser.java @@ -55,7 +55,8 @@ public class SchedulerChooser { } public void eventOccurred(Connection con) { - _log.log(Log.ERROR, "Yell at jrandom: Event occurred on " + con, new Exception("source")); + if (_log.shouldLog(Log.WARN)) + _log.warn("Yell at jrandom: Event occurred on " + con, new Exception("source")); } public boolean accept(Connection con) { return true; } }; diff --git a/core/java/src/net/i2p/util/ByteCache.java b/core/java/src/net/i2p/util/ByteCache.java index ac306cf89..992d4e5e6 100644 --- a/core/java/src/net/i2p/util/ByteCache.java +++ b/core/java/src/net/i2p/util/ByteCache.java @@ -1,10 +1,10 @@ package net.i2p.util; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.data.ByteArray; @@ -36,12 +36,12 @@ public final class ByteCache { } private Log _log; /** list of available and available entries */ - private final List _available; + private Queue _available; private int _maxCached; private int _entrySize; private long _lastOverflow; - /** do we actually want to cache? */ + /** do we actually want to cache? Warning - setting to false may NPE, this should be fixed or removed */ private static final boolean _cache = true; /** how often do we cleanup the cache */ @@ -51,7 +51,7 @@ public final class ByteCache { private ByteCache(int maxCachedEntries, int entrySize) { if (_cache) - _available = new ArrayList(maxCachedEntries); + _available = new LinkedBlockingQueue(maxCachedEntries); _maxCached = maxCachedEntries; _entrySize = entrySize; _lastOverflow = -1; @@ -62,6 +62,12 @@ public final class ByteCache { private void resize(int maxCachedEntries) { if (_maxCached >= maxCachedEntries) return; _maxCached = maxCachedEntries; + // make a bigger one, move the cached items over + Queue newLBQ = new LinkedBlockingQueue(maxCachedEntries); + ByteArray ba; + while ((ba = _available.poll()) != null) + newLBQ.offer(ba); + _available = newLBQ; } /** @@ -70,10 +76,9 @@ public final class ByteCache { */ public final ByteArray acquire() { if (_cache) { - synchronized (_available) { - if (_available.size() > 0) - return (ByteArray)_available.remove(0); - } + ByteArray rv = _available.poll(); + if (rv != null) + return rv; } _lastOverflow = System.currentTimeMillis(); byte data[] = new byte[_entrySize]; @@ -100,10 +105,7 @@ public final class ByteCache { if (shouldZero) Arrays.fill(entry.getData(), (byte)0x0); - synchronized (_available) { - if (_available.size() < _maxCached) - _available.add(entry); - } + _available.offer(entry); } } @@ -112,13 +114,11 @@ public final class ByteCache { if (System.currentTimeMillis() - _lastOverflow > EXPIRE_PERIOD) { // we haven't exceeded the cache size in a few minutes, so lets // shrink the cache - synchronized (_available) { int toRemove = _available.size() / 2; for (int i = 0; i < toRemove; i++) - _available.remove(0); + _available.poll(); if ( (toRemove > 0) && (_log.shouldLog(Log.DEBUG)) ) _log.debug("Removing " + toRemove + " cached entries of size " + _entrySize); - } } } } diff --git a/installer/install.xml b/installer/install.xml index c4931cb90..6cc0b3ff8 100644 --- a/installer/install.xml +++ b/installer/install.xml @@ -12,10 +12,8 @@ + \n"); out.flush(); - int states[] = null; + //int states[] = null; int numRunners = 0; - synchronized (_queueRunners) { - states = new int[_queueRunners.size()]; + + { + //states = new int[_queueRunners.size()]; int i = 0; - for (Iterator iter = _queueRunners.values().iterator(); iter.hasNext(); i++) { - JobQueueRunner runner = (JobQueueRunner)iter.next(); - states[i] = runner.getState(); + for (Iterator iter = _queueRunners.values().iterator(); iter.hasNext(); i++) { + JobQueueRunner runner = iter.next(); + //states[i] = runner.getState(); Job job = runner.getCurrentJob(); if (job != null) { activeJobs.add(job); @@ -621,21 +640,21 @@ public class JobQueue { buf.append("


Active jobs: ").append(activeJobs.size()).append("
    \n"); for (int i = 0; i < activeJobs.size(); i++) { - Job j = (Job)activeJobs.get(i); + Job j = activeJobs.get(i); buf.append("
  1. [started ").append(DataHelper.formatDuration(now-j.getTiming().getStartAfter())).append(" ago]: "); buf.append(j.toString()).append("
  2. \n"); } buf.append("
\n"); buf.append("
Just finished jobs: ").append(justFinishedJobs.size()).append("
    \n"); for (int i = 0; i < justFinishedJobs.size(); i++) { - Job j = (Job)justFinishedJobs.get(i); + Job j = justFinishedJobs.get(i); buf.append("
  1. [finished ").append(DataHelper.formatDuration(now-j.getTiming().getActualEnd())).append(" ago]: "); buf.append(j.toString()).append("
  2. \n"); } buf.append("
\n"); buf.append("
Ready/waiting jobs: ").append(readyJobs.size()).append("
    \n"); for (int i = 0; i < readyJobs.size(); i++) { - Job j = (Job)readyJobs.get(i); + Job j = readyJobs.get(i); buf.append("
  1. [waiting "); buf.append(DataHelper.formatDuration(now-j.getTiming().getStartAfter())); buf.append("]: "); @@ -645,13 +664,13 @@ public class JobQueue { out.flush(); buf.append("
    Scheduled jobs: ").append(timedJobs.size()).append("
      \n"); - TreeMap ordered = new TreeMap(); + TreeMap ordered = new TreeMap(); for (int i = 0; i < timedJobs.size(); i++) { - Job j = (Job)timedJobs.get(i); + Job j = timedJobs.get(i); ordered.put(new Long(j.getTiming().getStartAfter()), j); } - for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) { - Job j = (Job)iter.next(); + for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) { + Job j = iter.next(); long time = j.getTiming().getStartAfter() - now; buf.append("
    1. ").append(j.getName()).append(" in "); buf.append(DataHelper.formatDuration(time)).append("
    2. \n"); @@ -685,13 +704,10 @@ public class JobQueue { long maxPendingTime = -1; long minPendingTime = -1; - TreeMap tstats = null; - synchronized (_jobStats) { - tstats = new TreeMap(_jobStats); - } + TreeMap tstats = new TreeMap(_jobStats); - for (Iterator iter = tstats.values().iterator(); iter.hasNext(); ) { - JobStats stats = (JobStats)iter.next(); + for (Iterator iter = tstats.values().iterator(); iter.hasNext(); ) { + JobStats stats = iter.next(); buf.append(""); buf.append("").append(stats.getName()).append(""); buf.append("").append(stats.getRuns()).append(""); diff --git a/router/java/src/net/i2p/router/JobQueueRunner.java b/router/java/src/net/i2p/router/JobQueueRunner.java index c6633dbb2..8b55a8c6c 100644 --- a/router/java/src/net/i2p/router/JobQueueRunner.java +++ b/router/java/src/net/i2p/router/JobQueueRunner.java @@ -23,12 +23,12 @@ class JobQueueRunner implements Runnable { _currentJob = null; _lastJob = null; _log = _context.logManager().getLog(JobQueueRunner.class); - _context.statManager().createRateStat("jobQueue.jobRun", "How long jobs take", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("jobQueue.jobRunSlow", "How long jobs that take over a second take", "JobQueue", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + _context.statManager().createRateStat("jobQueue.jobRun", "How long jobs take", "JobQueue", new long[] { 60*60*1000l, 24*60*60*1000l }); + _context.statManager().createRateStat("jobQueue.jobRunSlow", "How long jobs that take over a second take", "JobQueue", new long[] { 60*60*1000l, 24*60*60*1000l }); _context.statManager().createRateStat("jobQueue.jobLag", "How long jobs have to wait before running", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("jobQueue.jobWait", "How long does a job sit on the job queue?", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - _context.statManager().createRateStat("jobQueue.jobRunnerInactive", "How long are runners inactive?", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); - _state = 1; + _context.statManager().createRateStat("jobQueue.jobWait", "How long does a job sit on the job queue?", "JobQueue", new long[] { 60*60*1000l, 24*60*60*1000l }); + //_context.statManager().createRateStat("jobQueue.jobRunnerInactive", "How long are runners inactive?", "JobQueue", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l }); + //_state = 1; } final int getState() { return _state; } @@ -41,16 +41,16 @@ class JobQueueRunner implements Runnable { public long getLastBegin() { return _lastBegin; } public long getLastEnd() { return _lastEnd; } public void run() { - _state = 2; + //_state = 2; long lastActive = _context.clock().now(); long jobNum = 0; while ( (_keepRunning) && (_context.jobQueue().isAlive()) ) { - _state = 3; + //_state = 3; try { Job job = _context.jobQueue().getNext(); - _state = 4; + //_state = 4; if (job == null) { - _state = 5; + //_state = 5; if (_context.router().isAlive()) if (_log.shouldLog(Log.ERROR)) _log.error("getNext returned null - dead?"); @@ -60,14 +60,14 @@ class JobQueueRunner implements Runnable { long enqueuedTime = 0; if (job instanceof JobImpl) { - _state = 6; + //_state = 6; long when = ((JobImpl)job).getMadeReadyOn(); if (when <= 0) { - _state = 7; + //_state = 7; _log.error("Job was not made ready?! " + job, new Exception("Not made ready?!")); } else { - _state = 8; + //_state = 8; enqueuedTime = now - when; } } @@ -75,27 +75,27 @@ class JobQueueRunner implements Runnable { long betweenJobs = now - lastActive; _currentJob = job; _lastJob = null; - _state = 9; + //_state = 9; if (_log.shouldLog(Log.DEBUG)) _log.debug("Runner " + _id + " running job " + job.getJobId() + ": " + job.getName()); long origStartAfter = job.getTiming().getStartAfter(); long doStart = _context.clock().now(); - _state = 10; + //_state = 10; job.getTiming().start(); runCurrentJob(); job.getTiming().end(); - _state = 11; + //_state = 11; long duration = job.getTiming().getActualEnd() - job.getTiming().getActualStart(); long beforeUpdate = _context.clock().now(); - _state = 12; + //_state = 12; _context.jobQueue().updateStats(job, doStart, origStartAfter, duration); - _state = 13; + //_state = 13; long diff = _context.clock().now() - beforeUpdate; long lag = doStart - origStartAfter; if (lag < 0) lag = 0; - _context.statManager().addRateData("jobQueue.jobRunnerInactive", betweenJobs, betweenJobs); + //_context.statManager().addRateData("jobQueue.jobRunnerInactive", betweenJobs, betweenJobs); _context.statManager().addRateData("jobQueue.jobRun", duration, duration); _context.statManager().addRateData("jobQueue.jobLag", lag, 0); _context.statManager().addRateData("jobQueue.jobWait", enqueuedTime, enqueuedTime); @@ -107,7 +107,7 @@ class JobQueueRunner implements Runnable { + ") on job " + _currentJob); } - _state = 14; + //_state = 14; if (diff > 100) { if (_log.shouldLog(Log.WARN)) @@ -121,7 +121,7 @@ class JobQueueRunner implements Runnable { _currentJob = null; _lastEnd = lastActive; jobNum++; - _state = 15; + //_state = 15; //if ( (jobNum % 10) == 0) // System.gc(); @@ -130,22 +130,22 @@ class JobQueueRunner implements Runnable { _log.log(Log.CRIT, "WTF, error running?", t); } } - _state = 16; + //_state = 16; if (_context.router().isAlive()) if (_log.shouldLog(Log.CRIT)) _log.log(Log.CRIT, "Queue runner " + _id + " exiting"); _context.jobQueue().removeRunner(_id); - _state = 17; + //_state = 17; } private void runCurrentJob() { try { - _state = 18; + //_state = 18; _lastBegin = _context.clock().now(); _currentJob.runJob(); - _state = 19; + //_state = 19; } catch (OutOfMemoryError oom) { - _state = 20; + //_state = 20; try { if (_log.shouldLog(Log.CRIT)) _log.log(Log.CRIT, "Router ran out of memory, shutting down", oom); @@ -157,7 +157,7 @@ class JobQueueRunner implements Runnable { try { Thread.sleep(1000); } catch (InterruptedException ie) {} System.exit(-1); } catch (Throwable t) { - _state = 21; + //_state = 21; if (_log.shouldLog(Log.CRIT)) _log.log(Log.CRIT, "Error processing job [" + _currentJob.getName() + "] on thread " + _id + ": " + t.getMessage(), t); diff --git a/router/java/src/net/i2p/router/KeyManager.java b/router/java/src/net/i2p/router/KeyManager.java index 711759a40..a145a4f66 100644 --- a/router/java/src/net/i2p/router/KeyManager.java +++ b/router/java/src/net/i2p/router/KeyManager.java @@ -12,10 +12,10 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.DataFormatException; import net.i2p.data.DataStructure; @@ -41,7 +41,7 @@ public class KeyManager { private PublicKey _publicKey; private SigningPrivateKey _signingPrivateKey; private SigningPublicKey _signingPublicKey; - private final Map _leaseSetKeys; // Destination --> LeaseSetKeys + private final Map _leaseSetKeys; // Destination --> LeaseSetKeys private SynchronizeKeysJob _synchronizeJob; public final static String PROP_KEYDIR = "router.keyBackupDir"; @@ -63,7 +63,7 @@ public class KeyManager { setPublicKey(null); setSigningPrivateKey(null); setSigningPublicKey(null); - _leaseSetKeys = new HashMap(); + _leaseSetKeys = new ConcurrentHashMap(); } public void startup() { @@ -102,9 +102,7 @@ public class KeyManager { public void registerKeys(Destination dest, SigningPrivateKey leaseRevocationPrivateKey, PrivateKey endpointDecryptionKey) { _log.info("Registering keys for destination " + dest.calculateHash().toBase64()); LeaseSetKeys keys = new LeaseSetKeys(dest, leaseRevocationPrivateKey, endpointDecryptionKey); - synchronized (_leaseSetKeys) { - _leaseSetKeys.put(dest.calculateHash(), keys); - } + _leaseSetKeys.put(dest.calculateHash(), keys); } private void queueWrite() { @@ -118,27 +116,19 @@ public class KeyManager { public LeaseSetKeys unregisterKeys(Destination dest) { if (_log.shouldLog(Log.INFO)) _log.info("Unregistering keys for destination " + dest.calculateHash().toBase64()); - LeaseSetKeys rv = null; - synchronized (_leaseSetKeys) { - rv = (LeaseSetKeys)_leaseSetKeys.remove(dest.calculateHash()); - } - return rv; + return _leaseSetKeys.remove(dest.calculateHash()); } public LeaseSetKeys getKeys(Destination dest) { return getKeys(dest.calculateHash()); } public LeaseSetKeys getKeys(Hash dest) { - synchronized (_leaseSetKeys) { - return (LeaseSetKeys)_leaseSetKeys.get(dest); - } + return _leaseSetKeys.get(dest); } - public Set getAllKeys() { + public Set getAllKeys() { HashSet keys = new HashSet(); - synchronized (_leaseSetKeys) { - keys.addAll(_leaseSetKeys.values()); - } + keys.addAll(_leaseSetKeys.values()); return keys; } diff --git a/router/java/src/net/i2p/router/OutNetMessage.java b/router/java/src/net/i2p/router/OutNetMessage.java index e3188f363..10aa6ec15 100644 --- a/router/java/src/net/i2p/router/OutNetMessage.java +++ b/router/java/src/net/i2p/router/OutNetMessage.java @@ -46,18 +46,18 @@ public class OutNetMessage { private ReplyJob _onReply; private Job _onFailedReply; private MessageSelector _replySelector; - private Set _failedTransports; + private Set _failedTransports; private long _sendBegin; private long _transmitBegin; private Exception _createdBy; private long _created; /** for debugging, contains a mapping of even name to Long (e.g. "begin sending", "handleOutbound", etc) */ - private HashMap _timestamps; + private HashMap _timestamps; /** * contains a list of timestamp event names in the order they were fired * (some JVMs have less than 10ms resolution, so the Long above doesn't guarantee order) */ - private List _timestampOrder; + private List _timestampOrder; private int _queueSize; private long _prepareBegin; private long _prepareEnd; @@ -108,11 +108,11 @@ public class OutNetMessage { } return now - _created; } - public Map getTimestamps() { + public Map getTimestamps() { if (_log.shouldLog(Log.INFO)) { synchronized (this) { locked_initTimestamps(); - return (Map)_timestamps.clone(); + return (Map)_timestamps.clone(); } } return Collections.EMPTY_MAP; @@ -121,7 +121,7 @@ public class OutNetMessage { if (_log.shouldLog(Log.INFO)) { synchronized (this) { locked_initTimestamps(); - return (Long)_timestamps.get(eventName); + return _timestamps.get(eventName); } } return ZERO; @@ -339,8 +339,8 @@ public class OutNetMessage { synchronized (this) { long lastWhen = -1; for (int i = 0; i < _timestampOrder.size(); i++) { - String name = (String)_timestampOrder.get(i); - Long when = (Long)_timestamps.get(name); + String name = _timestampOrder.get(i); + Long when = _timestamps.get(name); buf.append("\t["); long diff = when.longValue() - lastWhen; if ( (lastWhen > 0) && (diff > 500) ) diff --git a/router/java/src/net/i2p/router/PeerManagerFacade.java b/router/java/src/net/i2p/router/PeerManagerFacade.java index 61b1d3799..f8f3cbf1e 100644 --- a/router/java/src/net/i2p/router/PeerManagerFacade.java +++ b/router/java/src/net/i2p/router/PeerManagerFacade.java @@ -25,8 +25,8 @@ public interface PeerManagerFacade extends Service { * * @return List of Hash objects of the RouterIdentity for matching peers */ - public List selectPeers(PeerSelectionCriteria criteria); - public List getPeersByCapability(char capability); + public List selectPeers(PeerSelectionCriteria criteria); + public List getPeersByCapability(char capability); public void setCapabilities(Hash peer, String caps); public void removeCapabilities(Hash peer); public Hash selectRandomByCapability(char capability); diff --git a/router/java/src/net/i2p/router/RouterThrottleImpl.java b/router/java/src/net/i2p/router/RouterThrottleImpl.java index 289f929a8..795e1636c 100644 --- a/router/java/src/net/i2p/router/RouterThrottleImpl.java +++ b/router/java/src/net/i2p/router/RouterThrottleImpl.java @@ -44,27 +44,27 @@ class RouterThrottleImpl implements RouterThrottle { _log = context.logManager().getLog(RouterThrottleImpl.class); setTunnelStatus(); _context.statManager().createRateStat("router.throttleNetworkCause", "How lagged the jobQueue was when an I2NP was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 }); - _context.statManager().createRateStat("router.throttleNetDbCause", "How lagged the jobQueue was when a networkDb request was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 }); - _context.statManager().createRateStat("router.throttleTunnelCause", "How lagged the jobQueue was when a tunnel request was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 }); + //_context.statManager().createRateStat("router.throttleNetDbCause", "How lagged the jobQueue was when a networkDb request was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 }); + //_context.statManager().createRateStat("router.throttleTunnelCause", "How lagged the jobQueue was when a tunnel request was throttled", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("tunnel.bytesAllocatedAtAccept", "How many bytes had been 'allocated' for participating tunnels when we accepted a request?", "Tunnels", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelProcessingTime1m", "How long it takes to process a message (1 minute average) when we throttle a tunnel?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelProcessingTime10m", "How long it takes to process a message (10 minute average) when we throttle a tunnel?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelMaxExceeded", "How many tunnels we are participating in when we refuse one due to excees?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelProbTooFast", "How many tunnels beyond the previous 1h average are we participating in when we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); - _context.statManager().createRateStat("router.throttleTunnelProbTestSlow", "How slow are our tunnel tests when our average exceeds the old average and we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); + //_context.statManager().createRateStat("router.throttleTunnelProbTestSlow", "How slow are our tunnel tests when our average exceeds the old average and we throttle?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelBandwidthExceeded", "How much bandwidth is allocated when we refuse due to bandwidth allocation?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelBytesAllowed", "How many bytes are allowed to be sent when we get a tunnel request (period is how many are currently allocated)?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelBytesUsed", "Used Bps at request (period = max KBps)?", "Throttle", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 }); _context.statManager().createRateStat("router.throttleTunnelFailCount1m", "How many messages failed to be sent in the last 2 minutes when we throttle based on a spike in failures (period = 10 minute average failure count)?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000}); - _context.statManager().createRateStat("router.throttleTunnelQueueOverload", "How many pending tunnel request messages have we received when we reject them due to overload (period = time to process each)?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000}); + //_context.statManager().createRateStat("router.throttleTunnelQueueOverload", "How many pending tunnel request messages have we received when we reject them due to overload (period = time to process each)?", "Throttle", new long[] { 60*1000, 10*60*1000, 60*60*1000}); } public boolean acceptNetworkMessage() { //if (true) return true; long lag = _context.jobQueue().getMaxLag(); if ( (lag > JOB_LAG_LIMIT) && (_context.router().getUptime() > 60*1000) ) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Throttling network reader, as the job lag is " + lag); + if (_log.shouldLog(Log.WARN)) + _log.warn("Throttling network reader, as the job lag is " + lag); _context.statManager().addRateData("router.throttleNetworkCause", lag, lag); return false; } else { diff --git a/router/java/src/net/i2p/router/peermanager/PeerManager.java b/router/java/src/net/i2p/router/peermanager/PeerManager.java index 009a6ed50..61f5c2062 100644 --- a/router/java/src/net/i2p/router/peermanager/PeerManager.java +++ b/router/java/src/net/i2p/router/peermanager/PeerManager.java @@ -11,12 +11,12 @@ package net.i2p.router.peermanager; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.Hash; import net.i2p.data.RouterInfo; @@ -26,6 +26,7 @@ import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; import net.i2p.util.SimpleTimer; +import net.i2p.util.ConcurrentHashSet; /** * Manage the current state of the statistics @@ -46,8 +47,8 @@ class PeerManager { private RouterContext _context; private ProfileOrganizer _organizer; private ProfilePersistenceHelper _persistenceHelper; - private List _peersByCapability[]; - private final Map _capabilitiesByPeer; + private Set _peersByCapability[]; + private final Map _capabilitiesByPeer; public PeerManager(RouterContext context) { _context = context; @@ -55,10 +56,10 @@ class PeerManager { _persistenceHelper = new ProfilePersistenceHelper(context); _organizer = context.profileOrganizer(); _organizer.setUs(context.routerHash()); - _capabilitiesByPeer = new HashMap(128); - _peersByCapability = new List[26]; + _capabilitiesByPeer = new ConcurrentHashMap(128); + _peersByCapability = new Set[26]; for (int i = 0; i < _peersByCapability.length; i++) - _peersByCapability[i] = new ArrayList(64); + _peersByCapability[i] = new ConcurrentHashSet(); loadProfiles(); ////_context.jobQueue().addJob(new EvaluateProfilesJob(_context)); SimpleScheduler.getInstance().addPeriodicEvent(new Reorg(), 0, 45*1000); @@ -77,14 +78,16 @@ class PeerManager { void storeProfiles() { Set peers = selectPeers(); - for (Iterator iter = peers.iterator(); iter.hasNext(); ) { - Hash peer = (Hash)iter.next(); + for (Iterator iter = peers.iterator(); iter.hasNext(); ) { + Hash peer = iter.next(); storeProfile(peer); } } + Set selectPeers() { return _organizer.selectAllPeers(); } + void storeProfile(Hash peer) { if (peer == null) return; PeerProfile prof = _organizer.getProfile(peer); @@ -92,10 +95,11 @@ class PeerManager { if (true) _persistenceHelper.writeProfile(prof); } + void loadProfiles() { - Set profiles = _persistenceHelper.readProfiles(); - for (Iterator iter = profiles.iterator(); iter.hasNext();) { - PeerProfile prof = (PeerProfile)iter.next(); + Set profiles = _persistenceHelper.readProfiles(); + for (Iterator iter = profiles.iterator(); iter.hasNext();) { + PeerProfile prof = iter.next(); if (prof != null) { _organizer.addProfile(prof); if (_log.shouldLog(Log.DEBUG)) @@ -107,10 +111,11 @@ class PeerManager { /** * Find some peers that meet the criteria and we have the netDb info for locally * + * Only used by PeerTestJob (PURPOSE_TEST) */ - List selectPeers(PeerSelectionCriteria criteria) { - Set peers = new HashSet(criteria.getMinimumRequired()); - Set exclude = new HashSet(1); + List selectPeers(PeerSelectionCriteria criteria) { + Set peers = new HashSet(criteria.getMinimumRequired()); + Set exclude = new HashSet(1); exclude.add(_context.routerHash()); switch (criteria.getPurpose()) { case PeerSelectionCriteria.PURPOSE_TEST: @@ -143,10 +148,10 @@ class PeerManager { default: break; } - if (peers.size() <= 0) { + if (peers.isEmpty()) { if (_log.shouldLog(Log.WARN)) _log.warn("We ran out of peers when looking for reachable ones after finding " - + peers.size() + " with " + + "0 with " + _organizer.countWellIntegratedPeers() + "/" + _organizer.countHighCapacityPeers() + "/" + _organizer.countFastPeers() + " integrated/high capacity/fast peers"); @@ -160,18 +165,18 @@ class PeerManager { if (_log.shouldLog(Log.DEBUG)) _log.debug("Setting capabilities for " + peer.toBase64() + " to " + caps); if (caps != null) caps = caps.toLowerCase(); - synchronized (_capabilitiesByPeer) { + String oldCaps = null; if (caps != null) - oldCaps = (String)_capabilitiesByPeer.put(peer, caps); + oldCaps = _capabilitiesByPeer.put(peer, caps); else - oldCaps = (String)_capabilitiesByPeer.remove(peer); + oldCaps = _capabilitiesByPeer.remove(peer); if (oldCaps != null) { for (int i = 0; i < oldCaps.length(); i++) { char c = oldCaps.charAt(i); if ( (caps == null) || (caps.indexOf(c) < 0) ) { - List peers = locked_getPeers(c); + Set peers = locked_getPeers(c); if (peers != null) peers.remove(peer); } @@ -182,15 +187,15 @@ class PeerManager { char c = caps.charAt(i); if ( (oldCaps != null) && (oldCaps.indexOf(c) >= 0) ) continue; - List peers = locked_getPeers(c); - if ( (peers != null) && (!peers.contains(peer)) ) + Set peers = locked_getPeers(c); + if (peers != null) peers.add(peer); } } - } } - private List locked_getPeers(char c) { + /** locking no longer req'd */ + private Set locked_getPeers(char c) { c = Character.toLowerCase(c); int i = c - 'a'; if ( (i < 0) || (i >= _peersByCapability.length) ) { @@ -204,18 +209,19 @@ class PeerManager { public void removeCapabilities(Hash peer) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Removing capabilities from " + peer.toBase64()); - synchronized (_capabilitiesByPeer) { + String oldCaps = (String)_capabilitiesByPeer.remove(peer); if (oldCaps != null) { for (int i = 0; i < oldCaps.length(); i++) { char c = oldCaps.charAt(i); - List peers = locked_getPeers(c); + Set peers = locked_getPeers(c); if (peers != null) peers.remove(peer); } } - } } + +/******* public Hash selectRandomByCapability(char capability) { int index = _context.random().nextInt(Integer.MAX_VALUE); synchronized (_capabilitiesByPeer) { @@ -227,20 +233,29 @@ class PeerManager { } return null; } - public List getPeersByCapability(char capability) { - if (false) { - synchronized (_capabilitiesByPeer) { - List peers = locked_getPeers(capability); - if (peers != null) - return new ArrayList(peers); - } +********/ + + /** + * The only user of this is TunnelPeerSelector for unreachables? + */ + public List getPeersByCapability(char capability) { + if (true) { + Set peers = locked_getPeers(capability); + if (peers != null) + return new ArrayList(peers); return null; } else { + // Wow this looks really slow... + // What is the point of keeping all the data structures above + // if we are going to go through the whole netdb anyway? + // Not sure why jrandom switched to do it this way, + // the checkin comments aren't clear... + // Since the locking is gone, switch back to the above. FloodfillNetworkDatabaseFacade f = (FloodfillNetworkDatabaseFacade)_context.netDb(); - List routerInfos = f.getKnownRouterData(); - List rv = new ArrayList(); - for (Iterator iter = routerInfos.iterator(); iter.hasNext(); ) { - RouterInfo ri = (RouterInfo)iter.next(); + List routerInfos = f.getKnownRouterData(); + List rv = new ArrayList(); + for (Iterator iter = routerInfos.iterator(); iter.hasNext(); ) { + RouterInfo ri = iter.next(); String caps = ri.getCapabilities(); if (caps.indexOf(capability) >= 0) rv.add(ri.getIdentity().calculateHash()); diff --git a/router/java/src/net/i2p/router/peermanager/PeerManagerFacadeImpl.java b/router/java/src/net/i2p/router/peermanager/PeerManagerFacadeImpl.java index 69d5f11ce..3904101ce 100644 --- a/router/java/src/net/i2p/router/peermanager/PeerManagerFacadeImpl.java +++ b/router/java/src/net/i2p/router/peermanager/PeerManagerFacadeImpl.java @@ -57,7 +57,7 @@ public class PeerManagerFacadeImpl implements PeerManagerFacade { _manager.loadProfiles(); } - public List selectPeers(PeerSelectionCriteria criteria) { + public List selectPeers(PeerSelectionCriteria criteria) { return _manager.selectPeers(criteria); } @@ -69,11 +69,15 @@ public class PeerManagerFacadeImpl implements PeerManagerFacade { if (_manager == null) return; _manager.removeCapabilities(peer); } + + /** @deprecated unused */ public Hash selectRandomByCapability(char capability) { - if (_manager == null) return null; - return _manager.selectRandomByCapability(capability); + //if (_manager == null) return null; + //return _manager.selectRandomByCapability(capability); + return null; } - public List getPeersByCapability(char capability) { + + public List getPeersByCapability(char capability) { if (_manager == null) return new ArrayList(0); return _manager.getPeersByCapability(capability); } diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java index 2223f0404..03b49269f 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java @@ -217,7 +217,7 @@ public class ProfileOrganizer { return activePeers; } - private boolean isX(Map m, Hash peer) { + private boolean isX(Map m, Hash peer) { getReadLock(); try { return m.containsKey(peer); @@ -272,10 +272,10 @@ public class ProfileOrganizer { * @param matches set to store the return value in * */ - public void selectFastPeers(int howMany, Set exclude, Set matches) { + public void selectFastPeers(int howMany, Set exclude, Set matches) { selectFastPeers(howMany, exclude, matches, 0); } - public void selectFastPeers(int howMany, Set exclude, Set matches, int mask) { + public void selectFastPeers(int howMany, Set exclude, Set matches, int mask) { getReadLock(); try { locked_selectPeers(_fastPeers, howMany, exclude, matches, mask); @@ -295,10 +295,10 @@ public class ProfileOrganizer { * Return a set of Hashes for peers that have a high capacity * */ - public void selectHighCapacityPeers(int howMany, Set exclude, Set matches) { + public void selectHighCapacityPeers(int howMany, Set exclude, Set matches) { selectHighCapacityPeers(howMany, exclude, matches, 0); } - public void selectHighCapacityPeers(int howMany, Set exclude, Set matches, int mask) { + public void selectHighCapacityPeers(int howMany, Set exclude, Set matches, int mask) { getReadLock(); try { // we only use selectHighCapacityPeers when we are selecting for PURPOSE_TEST @@ -326,10 +326,10 @@ public class ProfileOrganizer { * Return a set of Hashes for peers that are well integrated into the network. * */ - public void selectWellIntegratedPeers(int howMany, Set exclude, Set matches) { + public void selectWellIntegratedPeers(int howMany, Set exclude, Set matches) { selectWellIntegratedPeers(howMany, exclude, matches, 0); } - public void selectWellIntegratedPeers(int howMany, Set exclude, Set matches, int mask) { + public void selectWellIntegratedPeers(int howMany, Set exclude, Set matches, int mask) { getReadLock(); try { locked_selectPeers(_wellIntegratedPeers, howMany, exclude, matches, mask); @@ -350,13 +350,13 @@ public class ProfileOrganizer { * we are already talking with * */ - public void selectNotFailingPeers(int howMany, Set exclude, Set matches) { + public void selectNotFailingPeers(int howMany, Set exclude, Set matches) { selectNotFailingPeers(howMany, exclude, matches, false, 0); } - public void selectNotFailingPeers(int howMany, Set exclude, Set matches, int mask) { + public void selectNotFailingPeers(int howMany, Set exclude, Set matches, int mask) { selectNotFailingPeers(howMany, exclude, matches, false, mask); } - public void selectNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) { + public void selectNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) { selectNotFailingPeers(howMany, exclude, matches, onlyNotFailing, 0); } /** @@ -368,7 +368,7 @@ public class ProfileOrganizer { * @param matches set to store the matches in * @param onlyNotFailing if true, don't include any high capacity peers */ - public void selectNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing, int mask) { + public void selectNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing, int mask) { if (matches.size() < howMany) selectAllNotFailingPeers(howMany, exclude, matches, onlyNotFailing, mask); return; @@ -388,7 +388,7 @@ public class ProfileOrganizer { * @param exclude non-null * No mask parameter, to be fixed */ - public void selectActiveNotFailingPeers(int howMany, Set exclude, Set matches) { + public void selectActiveNotFailingPeers(int howMany, Set exclude, Set matches) { if (matches.size() < howMany) { getReadLock(); try { @@ -412,7 +412,7 @@ public class ProfileOrganizer { * * This DOES cascade further to non-connected peers. */ - private void selectActiveNotFailingPeers2(int howMany, Set exclude, Set matches, int mask) { + private void selectActiveNotFailingPeers2(int howMany, Set exclude, Set matches, int mask) { if (matches.size() < howMany) { Map activePeers = new HashMap(); getReadLock(); @@ -439,14 +439,14 @@ public class ProfileOrganizer { * Return a set of Hashes for peers that are not failing. * */ - public void selectAllNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) { + public void selectAllNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing) { selectAllNotFailingPeers(howMany, exclude, matches, onlyNotFailing, 0); } /** * @param mask ignored, should call locked_selectPeers, to be fixed * */ - private void selectAllNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing, int mask) { + private void selectAllNotFailingPeers(int howMany, Set exclude, Set matches, boolean onlyNotFailing, int mask) { if (matches.size() < howMany) { int orig = matches.size(); int needed = howMany - orig; @@ -495,7 +495,7 @@ public class ProfileOrganizer { * I'm not quite sure why you'd want this... (other than for failover from the better results) * */ - public void selectFailingPeers(int howMany, Set exclude, Set matches) { + public void selectFailingPeers(int howMany, Set exclude, Set matches) { getReadLock(); try { locked_selectPeers(_failingPeers, howMany, exclude, matches); @@ -564,12 +564,12 @@ public class ProfileOrganizer { * recent == last 20s * */ - public List selectPeersRecentlyRejecting() { + public List selectPeersRecentlyRejecting() { getReadLock(); try { long cutoff = _context.clock().now() - (20*1000); int count = _notFailingPeers.size(); - List l = new ArrayList(count / 128); + List l = new ArrayList(count / 128); for (Iterator iter = _notFailingPeers.values().iterator(); iter.hasNext(); ) { PeerProfile prof = iter.next(); if (prof.getTunnelHistory().getLastRejectedBandwidth() > cutoff) @@ -583,10 +583,10 @@ public class ProfileOrganizer { * Find the hashes for all peers we are actively profiling * */ - public Set selectAllPeers() { + public Set selectAllPeers() { getReadLock(); try { - Set allPeers = new HashSet(_failingPeers.size() + _notFailingPeers.size() + _highCapacityPeers.size() + _fastPeers.size()); + Set allPeers = new HashSet(_failingPeers.size() + _notFailingPeers.size() + _highCapacityPeers.size() + _fastPeers.size()); allPeers.addAll(_failingPeers.keySet()); allPeers.addAll(_notFailingPeers.keySet()); allPeers.addAll(_highCapacityPeers.keySet()); @@ -853,10 +853,10 @@ public class ProfileOrganizer { * high capacity group to define the integration threshold. * */ - private void locked_calculateThresholds(Set allPeers) { + private void locked_calculateThresholds(Set allPeers) { double totalCapacity = 0; double totalIntegration = 0; - Set reordered = new TreeSet(_comp); + Set reordered = new TreeSet(_comp); for (Iterator iter = allPeers.iterator(); iter.hasNext(); ) { PeerProfile profile = iter.next(); @@ -895,7 +895,7 @@ public class ProfileOrganizer { * (highest first) for active nonfailing peers whose * capacity is greater than the growth factor */ - private void locked_calculateCapacityThreshold(double totalCapacity, Set reordered) { + private void locked_calculateCapacityThreshold(double totalCapacity, Set reordered) { int numNotFailing = reordered.size(); double meanCapacity = avg(totalCapacity, numNotFailing); @@ -964,7 +964,7 @@ public class ProfileOrganizer { * @param reordered ordered set of PeerProfile objects, ordered by capacity * (highest first) for active nonfailing peers */ - private void locked_calculateSpeedThreshold(Set reordered) { + private void locked_calculateSpeedThreshold(Set reordered) { if (true) { locked_calculateSpeedThresholdMean(reordered); return; @@ -996,7 +996,7 @@ public class ProfileOrganizer { *****/ } - private void locked_calculateSpeedThresholdMean(Set reordered) { + private void locked_calculateSpeedThresholdMean(Set reordered) { double total = 0; int count = 0; for (Iterator iter = reordered.iterator(); iter.hasNext(); ) { @@ -1040,10 +1040,10 @@ public class ProfileOrganizer { * matches set until it has howMany elements in it. * */ - private void locked_selectPeers(Map peers, int howMany, Set toExclude, Set matches) { + private void locked_selectPeers(Map peers, int howMany, Set toExclude, Set matches) { locked_selectPeers(peers, howMany, toExclude, matches, 0); } - private void locked_selectPeers(Map peers, int howMany, Set toExclude, Set matches, int mask) { + private void locked_selectPeers(Map peers, int howMany, Set toExclude, Set matches, int mask) { List all = new ArrayList(peers.keySet()); if (toExclude != null) all.removeAll(toExclude); @@ -1051,7 +1051,7 @@ public class ProfileOrganizer { all.removeAll(matches); all.remove(_us); Collections.shuffle(all, _random); - Set IPSet = new HashSet(8); + Set IPSet = new HashSet(8); for (int i = 0; (matches.size() < howMany) && (i < all.size()); i++) { Hash peer = (Hash)all.get(i); boolean ok = isSelectable(peer); @@ -1073,8 +1073,8 @@ public class ProfileOrganizer { * @param mask is 1-4 (number of bytes to match) * @param IPMatches all IPs so far, modified by this routine */ - private boolean notRestricted(Hash peer, Set IPSet, int mask) { - Set peerIPs = maskedIPSet(peer, mask); + private boolean notRestricted(Hash peer, Set IPSet, int mask) { + Set peerIPs = maskedIPSet(peer, mask); if (containsAny(IPSet, peerIPs)) return false; IPSet.addAll(peerIPs); @@ -1087,8 +1087,8 @@ public class ProfileOrganizer { * * @return an opaque set of masked IPs for this peer */ - private Set maskedIPSet(Hash peer, int mask) { - Set rv = new HashSet(2); + private Set maskedIPSet(Hash peer, int mask) { + Set rv = new HashSet(2); byte[] commIP = _context.commSystem().getIP(peer); if (commIP != null) rv.add(maskedIP(commIP, mask)); diff --git a/router/java/src/net/i2p/router/startup/ClientAppConfig.java b/router/java/src/net/i2p/router/startup/ClientAppConfig.java index a387228e4..982d4a298 100644 --- a/router/java/src/net/i2p/router/startup/ClientAppConfig.java +++ b/router/java/src/net/i2p/router/startup/ClientAppConfig.java @@ -18,6 +18,49 @@ import net.i2p.router.RouterContext; * so they can be used both by LoadClientAppsJob and by the configuration * page in the router console. * + *
      + *
      + * clients.config format:
      + *
      + * Lines are of the form clientApp.x.prop=val, where x is the app number.
      + * App numbers MUST start with 0 and be consecutive.
      + *
      + * Properties are as follows:
      + *	main: Full class name. Required. The main() method in this
      + *	      class will be run.
      + *	name: Name to be displayed on console.
      + *	args: Arguments to the main class, separated by spaces or tabs.
      + *	      Arguments containing spaces or tabs may be quoted with ' or "
      + *	delay: Seconds before starting, default 120
      + *	onBoot: {true|false}, default false, forces a delay of 0,
      + *	        overrides delay setting
      + *	startOnLoad: {true|false} Is the client to be run at all?
      + *                    Default true
      + *
      + * The following additional properties are used only by plugins:
      + *	stopargs: Arguments to stop the client.
      + *	uninstallargs: Arguments to stop the client.
      + *	classpath: Additional classpath elements for the client,
      + *	           separated by commas.
      + *
      + * The following substitutions are made in the args, stopargs,
      + * uninstallargs, and classpath lines, for plugins only:
      + *	$I2P: The base I2P install directory
      + *	$CONFIG: The user's configuration directory (e.g. ~/.i2p)
      + *	$PLUGIN: This plugin's directory (e.g. ~/.i2p/plugins/foo)
      + *
      + * All properties except "main" are optional.
      + * Lines starting with "#" are comments.
      + *
      + * If the delay is less than zero, the client is run immediately,
      + * in the same thread, so that exceptions may be propagated to the console.
      + * In this case, the client should either throw an exception, return quickly,
      + * or spawn its own thread.
      + * If the delay is greater than or equal to zero, it will be run
      + * in a new thread, and exceptions will be logged but not propagated
      + * to the console.
      + *
      + * 
      */ public class ClientAppConfig { /** wait 2 minutes before starting up client apps */ diff --git a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java index 9be0885ae..1f68c147d 100644 --- a/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java +++ b/router/java/src/net/i2p/router/startup/LoadClientAppsJob.java @@ -11,7 +11,7 @@ import net.i2p.util.I2PThread; import net.i2p.util.Log; /** - * Run any client applications specified in the router.config. If any clientApp + * Run any client applications specified in clients.config. If any clientApp * contains the config property ".onBoot=true" it'll be launched immediately, otherwise * it'll get queued up for starting 2 minutes later. * @@ -40,7 +40,7 @@ public class LoadClientAppsJob extends JobImpl { if (app.disabled) continue; String argVal[] = parseArgs(app.args); - if (app.delay == 0) { + if (app.delay <= 0) { // run this guy now runClient(app.className, app.clientName, argVal, _log); } else { @@ -118,6 +118,36 @@ public class LoadClientAppsJob extends JobImpl { return rv; } + /** + * Use to test if the class is present, + * to propagate an error back to the user, + * since runClient() runs in a separate thread. + * + * @since 0.7.13 + */ + public static void testClient(String className) throws ClassNotFoundException { + Class.forName(className); + } + + /** + * Run client in this thread. + * + * @throws just about anything, caller would be wise to catch Throwable + * @since 0.7.13 + */ + public static void runClientInline(String className, String clientName, String args[], Log log) throws Exception { + if (log.shouldLog(Log.INFO)) + log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args)); + if (args == null) + args = new String[0]; + Class cls = Class.forName(className); + Method method = cls.getMethod("main", new Class[] { String[].class }); + method.invoke(cls, new Object[] { args }); + } + + /** + * Run client in a new thread. + */ public static void runClient(String className, String clientName, String args[], Log log) { if (log.shouldLog(Log.INFO)) log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args)); diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index 0eb2110d0..d8480be1e 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -116,7 +116,7 @@ public class CommSystemFacadeImpl extends CommSystemFacade { return sum * 1000 / frameSize; } - public List getBids(OutNetMessage msg) { + public List getBids(OutNetMessage msg) { return _manager.getBids(msg); } public TransportBid getBid(OutNetMessage msg) { @@ -174,8 +174,8 @@ public class CommSystemFacadeImpl extends CommSystemFacade { } @Override - public Set createAddresses() { - Map addresses = null; + public Set createAddresses() { + Map addresses = null; boolean newCreated = false; if (_manager != null) { diff --git a/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java b/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java index 1d2472ecd..e9cde0b9c 100644 --- a/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java +++ b/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java @@ -4,24 +4,45 @@ import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import net.i2p.I2PAppContext; import net.i2p.util.I2PThread; import net.i2p.util.Log; +/** + * Concurrent plan: + * + * It's difficult to get rid of the locks on _pendingInboundRequests + * since locked_satisyInboundAvailable() leaves Requests on the head + * of the queue. + * + * When we go to Java 6, we can convert from a locked ArrayList to + * a LinkedBlockingDeque, where locked_sIA will poll() from the + * head of the queue, and if the request is not fully satisfied, + * offerFirst() (i.e. push) it back on the head. + * + * Ditto outbound of course. + * + * In the meantime, for Java 5, we have lockless 'shortcut' + * methods for the common case where we are under the bandwidth limits. + * And the volatile counters are now AtomicIntegers / AtomicLongs. + * + */ public class FIFOBandwidthLimiter { private Log _log; private I2PAppContext _context; - private final List _pendingInboundRequests; - private final List _pendingOutboundRequests; + private final List _pendingInboundRequests; + private final List _pendingOutboundRequests; /** how many bytes we can consume for inbound transmission immediately */ - private volatile int _availableInbound; + private AtomicInteger _availableInbound = new AtomicInteger(); /** how many bytes we can consume for outbound transmission immediately */ - private volatile int _availableOutbound; + private AtomicInteger _availableOutbound = new AtomicInteger(); /** how many bytes we can queue up for bursting */ - private volatile int _unavailableInboundBurst; + private AtomicInteger _unavailableInboundBurst = new AtomicInteger(); /** how many bytes we can queue up for bursting */ - private volatile int _unavailableOutboundBurst; + private AtomicInteger _unavailableOutboundBurst = new AtomicInteger(); /** how large _unavailableInbound can get */ private int _maxInboundBurst; /** how large _unavailableInbound can get */ @@ -35,13 +56,13 @@ public class FIFOBandwidthLimiter { /** shortcut of whether our inbound rate is unlimited */ private boolean _inboundUnlimited; /** lifetime counter of bytes received */ - private volatile long _totalAllocatedInboundBytes; + private AtomicLong _totalAllocatedInboundBytes = new AtomicLong(); /** lifetime counter of bytes sent */ - private volatile long _totalAllocatedOutboundBytes; + private AtomicLong _totalAllocatedOutboundBytes = new AtomicLong(); /** lifetime counter of tokens available for use but exceeded our maxInboundBurst size */ - private volatile long _totalWastedInboundBytes; + private AtomicLong _totalWastedInboundBytes = new AtomicLong(); /** lifetime counter of tokens available for use but exceeded our maxOutboundBurst size */ - private volatile long _totalWastedOutboundBytes; + private AtomicLong _totalWastedOutboundBytes = new AtomicLong(); private FIFOBandwidthRefiller _refiller; private long _lastTotalSent; @@ -75,8 +96,8 @@ public class FIFOBandwidthLimiter { } _pendingInboundRequests = new ArrayList(16); _pendingOutboundRequests = new ArrayList(16); - _lastTotalSent = _totalAllocatedOutboundBytes; - _lastTotalReceived = _totalAllocatedInboundBytes; + _lastTotalSent = _totalAllocatedOutboundBytes.get(); + _lastTotalReceived = _totalAllocatedInboundBytes.get(); _sendBps = 0; _recvBps = 0; _lastStatsUpdated = now(); @@ -90,10 +111,10 @@ public class FIFOBandwidthLimiter { //public long getAvailableInboundBytes() { return _availableInboundBytes; } //public long getAvailableOutboundBytes() { return _availableOutboundBytes; } - public long getTotalAllocatedInboundBytes() { return _totalAllocatedInboundBytes; } - public long getTotalAllocatedOutboundBytes() { return _totalAllocatedOutboundBytes; } - public long getTotalWastedInboundBytes() { return _totalWastedInboundBytes; } - public long getTotalWastedOutboundBytes() { return _totalWastedOutboundBytes; } + public long getTotalAllocatedInboundBytes() { return _totalAllocatedInboundBytes.get(); } + public long getTotalAllocatedOutboundBytes() { return _totalAllocatedOutboundBytes.get(); } + public long getTotalWastedInboundBytes() { return _totalWastedInboundBytes.get(); } + public long getTotalWastedOutboundBytes() { return _totalWastedOutboundBytes.get(); } //public long getMaxInboundBytes() { return _maxInboundBytes; } //public void setMaxInboundBytes(int numBytes) { _maxInboundBytes = numBytes; } //public long getMaxOutboundBytes() { return _maxOutboundBytes; } @@ -116,14 +137,14 @@ public class FIFOBandwidthLimiter { public void reinitialize() { _pendingInboundRequests.clear(); _pendingOutboundRequests.clear(); - _availableInbound = 0; - _availableOutbound = 0; + _availableInbound.set(0); + _availableOutbound.set(0); _maxInbound = 0; _maxOutbound = 0; _maxInboundBurst = 0; _maxOutboundBurst = 0; - _unavailableInboundBurst = 0; - _unavailableOutboundBurst = 0; + _unavailableInboundBurst.set(0); + _unavailableOutboundBurst.set(0); _inboundUnlimited = false; _outboundUnlimited = false; _refiller.reinitialize(); @@ -132,58 +153,66 @@ public class FIFOBandwidthLimiter { public Request createRequest() { return new SimpleRequest(); } /** - * Request some bytes, blocking until they become available - * + * Request some bytes. Does not block. */ - public Request requestInbound(int bytesIn, String purpose) { return requestInbound(bytesIn, purpose, null, null); } - public Request requestInbound(int bytesIn, String purpose, CompleteListener lsnr, Object attachment) { - if (_inboundUnlimited) { - _totalAllocatedInboundBytes += bytesIn; + public Request requestInbound(int bytesIn, String purpose) { + // try to satisfy without grabbing the global lock + if (shortcutSatisfyInboundRequest(bytesIn)) return _noop; - } - + return requestInbound(bytesIn, purpose, null, null); + } + + public Request requestInbound(int bytesIn, String purpose, CompleteListener lsnr, Object attachment) { SimpleRequest req = new SimpleRequest(bytesIn, 0, purpose, lsnr, attachment); requestInbound(req, bytesIn, purpose); return req; } - public void requestInbound(Request req, int bytesIn, String purpose) { - req.init(bytesIn, 0, purpose); - if (false) { ((SimpleRequest)req).allocateAll(); return; } - int pending = 0; + + /** + * The transports don't use this any more, so make it private + * and a SimpleRequest instead of a Request + * So there's no more casting + */ + private void requestInbound(SimpleRequest req, int bytesIn, String purpose) { + // don't init twice - uncomment if we make public again? + //req.init(bytesIn, 0, purpose); + int pending; synchronized (_pendingInboundRequests) { pending = _pendingInboundRequests.size(); _pendingInboundRequests.add(req); } - satisfyInboundRequests(((SimpleRequest)req).satisfiedBuffer); - ((SimpleRequest)req).satisfiedBuffer.clear(); + satisfyInboundRequests(req.satisfiedBuffer); + req.satisfiedBuffer.clear(); if (pending > 0) _context.statManager().addRateData("bwLimiter.pendingInboundRequests", pending, pending); } - /** - * Request some bytes, blocking until they become available - * - */ - public Request requestOutbound(int bytesOut, String purpose) { return requestOutbound(bytesOut, purpose, null, null); } - public Request requestOutbound(int bytesOut, String purpose, CompleteListener lsnr, Object attachment) { - if (_outboundUnlimited) { - _totalAllocatedOutboundBytes += bytesOut; - return _noop; - } + /** + * Request some bytes. Does not block. + */ + public Request requestOutbound(int bytesOut, String purpose) { + // try to satisfy without grabbing the global lock + if (shortcutSatisfyOutboundRequest(bytesOut)) + return _noop; + return requestOutbound(bytesOut, purpose, null, null); + } + + public Request requestOutbound(int bytesOut, String purpose, CompleteListener lsnr, Object attachment) { SimpleRequest req = new SimpleRequest(0, bytesOut, purpose, lsnr, attachment); requestOutbound(req, bytesOut, purpose); return req; } - public void requestOutbound(Request req, int bytesOut, String purpose) { - req.init(0, bytesOut, purpose); - if (false) { ((SimpleRequest)req).allocateAll(); return; } - int pending = 0; + + private void requestOutbound(SimpleRequest req, int bytesOut, String purpose) { + // don't init twice - uncomment if we make public again? + //req.init(0, bytesOut, purpose); + int pending; synchronized (_pendingOutboundRequests) { pending = _pendingOutboundRequests.size(); _pendingOutboundRequests.add(req); } - satisfyOutboundRequests(((SimpleRequest)req).satisfiedBuffer); - ((SimpleRequest)req).satisfiedBuffer.clear(); + satisfyOutboundRequests(req.satisfiedBuffer); + req.satisfiedBuffer.clear(); if (pending > 0) _context.statManager().addRateData("bwLimiter.pendingOutboundRequests", pending, pending); } @@ -200,7 +229,7 @@ public class FIFOBandwidthLimiter { void setOutboundBurstBytes(int bytes) { _maxOutboundBurst = bytes; } StringBuilder getStatus() { - StringBuilder rv = new StringBuilder(64); + StringBuilder rv = new StringBuilder(128); rv.append("Available: ").append(_availableInbound).append('/').append(_availableOutbound).append(' '); rv.append("Max: ").append(_maxInbound).append('/').append(_maxOutbound).append(' '); rv.append("Burst: ").append(_unavailableInboundBurst).append('/').append(_unavailableOutboundBurst).append(' '); @@ -215,64 +244,72 @@ public class FIFOBandwidthLimiter { * @param maxBurstIn allow up to this many bytes in from the burst section for this time period (may be negative) * @param maxBurstOut allow up to this many bytes in from the burst section for this time period (may be negative) */ - final void refillBandwidthQueues(List buf, long bytesInbound, long bytesOutbound, long maxBurstIn, long maxBurstOut) { + final void refillBandwidthQueues(List buf, long bytesInbound, long bytesOutbound, long maxBurstIn, long maxBurstOut) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Refilling the queues with " + bytesInbound + "/" + bytesOutbound + ": " + getStatus().toString()); - _availableInbound += bytesInbound; - _availableOutbound += bytesOutbound; - - if (_availableInbound > _maxInbound) { + + // Take some care throughout to minimize accesses to the atomics, + // both for efficiency and to not let strange things happen if + // it changes out from under us + // This never had locks before concurrent, anyway + + int avi = _availableInbound.addAndGet((int) bytesInbound); + if (avi > _maxInbound) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("available inbound (" + _availableInbound + ") exceeds our inbound burst (" + _maxInbound + "), so no supplement"); - _unavailableInboundBurst += _availableInbound - _maxInbound; - _availableInbound = _maxInbound; - if (_unavailableInboundBurst > _maxInboundBurst) { - _totalWastedInboundBytes += _unavailableInboundBurst - _maxInboundBurst; - _unavailableInboundBurst = _maxInboundBurst; + _log.debug("available inbound (" + avi + ") exceeds our inbound burst (" + _maxInbound + "), so no supplement"); + int uib = _unavailableInboundBurst.addAndGet(avi - _maxInbound); + _availableInbound.set(_maxInbound); + if (uib > _maxInboundBurst) { + _totalWastedInboundBytes.addAndGet(uib - _maxInboundBurst); + _unavailableInboundBurst.set(_maxInboundBurst); } } else { // try to pull in up to 1/10th of the burst rate, since we refill every 100ms int want = (int)maxBurstIn; - if (want > (_maxInbound - _availableInbound)) - want = _maxInbound - _availableInbound; + if (want > (_maxInbound - avi)) + want = _maxInbound - avi; if (_log.shouldLog(Log.DEBUG)) - _log.debug("want to pull " + want + " from the inbound burst (" + _unavailableInboundBurst + ") to supplement " + _availableInbound + " (max: " + _maxInbound + ")"); + _log.debug("want to pull " + want + " from the inbound burst (" + _unavailableInboundBurst + ") to supplement " + avi + " (max: " + _maxInbound + ")"); if (want > 0) { - if (want <= _unavailableInboundBurst) { - _availableInbound += want; - _unavailableInboundBurst -= want; + int uib = _unavailableInboundBurst.get(); + if (want <= uib) { + _availableInbound.addAndGet(want); + _unavailableInboundBurst.addAndGet(0 - want); } else { - _availableInbound += _unavailableInboundBurst; - _unavailableInboundBurst = 0; + _availableInbound.addAndGet(uib); + _unavailableInboundBurst.set(0); } } } - if (_availableOutbound > _maxOutbound) { + int avo = _availableOutbound.addAndGet((int) bytesOutbound); + if (avo > _maxOutbound) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("available outbound (" + _availableOutbound + ") exceeds our outbound burst (" + _maxOutbound + "), so no supplement"); - _unavailableOutboundBurst += _availableOutbound - _maxOutbound; - _availableOutbound = _maxOutbound; - if (_unavailableOutboundBurst > _maxOutboundBurst) { - _totalWastedOutboundBytes += _unavailableOutboundBurst - _maxOutboundBurst; - _unavailableOutboundBurst = _maxOutboundBurst; + _log.debug("available outbound (" + avo + ") exceeds our outbound burst (" + _maxOutbound + "), so no supplement"); + int uob = _unavailableOutboundBurst.getAndAdd(avo - _maxOutbound); + _availableOutbound.set(_maxOutbound); + + if (uob > _maxOutboundBurst) { + _totalWastedOutboundBytes.getAndAdd(uob - _maxOutboundBurst); + _unavailableOutboundBurst.set(_maxOutboundBurst); } } else { // try to pull in up to 1/10th of the burst rate, since we refill every 100ms int want = (int)maxBurstOut; - if (want > (_maxOutbound - _availableOutbound)) - want = _maxOutbound - _availableOutbound; + if (want > (_maxOutbound - avo)) + want = _maxOutbound - avo; if (_log.shouldLog(Log.DEBUG)) - _log.debug("want to pull " + want + " from the outbound burst (" + _unavailableOutboundBurst + ") to supplement " + _availableOutbound + " (max: " + _maxOutbound + ")"); + _log.debug("want to pull " + want + " from the outbound burst (" + _unavailableOutboundBurst + ") to supplement " + avo + " (max: " + _maxOutbound + ")"); if (want > 0) { - if (want <= _unavailableOutboundBurst) { - _availableOutbound += want; - _unavailableOutboundBurst -= want; + int uob = _unavailableOutboundBurst.get(); + if (want <= uob) { + _availableOutbound.addAndGet(want); + _unavailableOutboundBurst.addAndGet(0 - want); } else { - _availableOutbound += _unavailableOutboundBurst; - _unavailableOutboundBurst = 0; + _availableOutbound.addAndGet(uob); + _unavailableOutboundBurst.set(0); } } } @@ -286,8 +323,8 @@ public class FIFOBandwidthLimiter { long time = now - _lastStatsUpdated; // If at least one second has passed if (time >= 1000) { - long totS = _totalAllocatedOutboundBytes; - long totR = _totalAllocatedInboundBytes; + long totS = _totalAllocatedOutboundBytes.get(); + long totR = _totalAllocatedInboundBytes.get(); long sent = totS - _lastTotalSent; // How much we sent meanwhile long recv = totR - _lastTotalReceived; // How much we received meanwhile _lastTotalSent = totS; @@ -337,20 +374,22 @@ public class FIFOBandwidthLimiter { /** * Go through the queue, satisfying as many requests as possible (notifying * each one satisfied that the request has been granted). + * + * @param buffer returned with the satisfied outbound requests only */ - private final void satisfyRequests(List buffer) { + private final void satisfyRequests(List buffer) { buffer.clear(); satisfyInboundRequests(buffer); buffer.clear(); satisfyOutboundRequests(buffer); } - private final void satisfyInboundRequests(List satisfied) { + private final void satisfyInboundRequests(List satisfied) { synchronized (_pendingInboundRequests) { if (_inboundUnlimited) { locked_satisfyInboundUnlimited(satisfied); } else { - if (_availableInbound > 0) { + if (_availableInbound.get() > 0) { locked_satisfyInboundAvailable(satisfied); } else { // no bandwidth available @@ -370,6 +409,7 @@ public class FIFOBandwidthLimiter { } } + /** called from debug logging only */ private long locked_getLongestInboundWait() { long start = -1; for (int i = 0; i < _pendingInboundRequests.size(); i++) { @@ -382,6 +422,8 @@ public class FIFOBandwidthLimiter { else return now() - start; } + + /** called from debug logging only */ private long locked_getLongestOutboundWait() { long start = -1; for (int i = 0; i < _pendingOutboundRequests.size(); i++) { @@ -400,11 +442,11 @@ public class FIFOBandwidthLimiter { * There are no limits, so just give every inbound request whatever they want * */ - private final void locked_satisfyInboundUnlimited(List satisfied) { + private final void locked_satisfyInboundUnlimited(List satisfied) { while (_pendingInboundRequests.size() > 0) { SimpleRequest req = (SimpleRequest)_pendingInboundRequests.remove(0); int allocated = req.getPendingInboundRequested(); - _totalAllocatedInboundBytes += allocated; + _totalAllocatedInboundBytes.addAndGet(allocated); req.allocateBytes(allocated, 0); satisfied.add(req); long waited = now() - req.getRequestTime(); @@ -425,9 +467,9 @@ public class FIFOBandwidthLimiter { * * @return list of requests that were completely satisfied */ - private final void locked_satisfyInboundAvailable(List satisfied) { + private final void locked_satisfyInboundAvailable(List satisfied) { for (int i = 0; i < _pendingInboundRequests.size(); i++) { - if (_availableInbound <= 0) break; + if (_availableInbound.get() <= 0) break; SimpleRequest req = (SimpleRequest)_pendingInboundRequests.get(i); long waited = now() - req.getRequestTime(); if (req.getAborted()) { @@ -452,13 +494,14 @@ public class FIFOBandwidthLimiter { } // ok, they are really waiting for us to give them stuff int requested = req.getPendingInboundRequested(); - int allocated = 0; - if (_availableInbound > requested) + int avi = _availableInbound.get(); + int allocated; + if (avi >= requested) allocated = requested; else - allocated = _availableInbound; - _availableInbound -= allocated; - _totalAllocatedInboundBytes += allocated; + allocated = avi; + _availableInbound.addAndGet(0 - allocated); + _totalAllocatedInboundBytes.addAndGet(allocated); req.allocateBytes(allocated, 0); satisfied.add(req); if (req.getPendingInboundRequested() > 0) { @@ -485,12 +528,12 @@ public class FIFOBandwidthLimiter { } } - private final void satisfyOutboundRequests(List satisfied) { + private final void satisfyOutboundRequests(List satisfied) { synchronized (_pendingOutboundRequests) { if (_outboundUnlimited) { locked_satisfyOutboundUnlimited(satisfied); } else { - if (_availableOutbound > 0) { + if (_availableOutbound.get() > 0) { locked_satisfyOutboundAvailable(satisfied); } else { // no bandwidth available @@ -514,11 +557,11 @@ public class FIFOBandwidthLimiter { * There are no limits, so just give every outbound request whatever they want * */ - private final void locked_satisfyOutboundUnlimited(List satisfied) { + private final void locked_satisfyOutboundUnlimited(List satisfied) { while (_pendingOutboundRequests.size() > 0) { SimpleRequest req = (SimpleRequest)_pendingOutboundRequests.remove(0); int allocated = req.getPendingOutboundRequested(); - _totalAllocatedOutboundBytes += allocated; + _totalAllocatedOutboundBytes.addAndGet(allocated); req.allocateBytes(0, allocated); satisfied.add(req); long waited = now() - req.getRequestTime(); @@ -540,9 +583,9 @@ public class FIFOBandwidthLimiter { * * @return list of requests that were completely satisfied */ - private final void locked_satisfyOutboundAvailable(List satisfied) { + private final void locked_satisfyOutboundAvailable(List satisfied) { for (int i = 0; i < _pendingOutboundRequests.size(); i++) { - if (_availableOutbound <= 0) break; + if (_availableOutbound.get() <= 0) break; SimpleRequest req = (SimpleRequest)_pendingOutboundRequests.get(i); long waited = now() - req.getRequestTime(); if (req.getAborted()) { @@ -567,13 +610,14 @@ public class FIFOBandwidthLimiter { } // ok, they are really waiting for us to give them stuff int requested = req.getPendingOutboundRequested(); - int allocated = 0; - if (_availableOutbound > requested) + int avo = _availableOutbound.get(); + int allocated; + if (avo >= requested) allocated = requested; else - allocated = _availableOutbound; - _availableOutbound -= allocated; - _totalAllocatedOutboundBytes += allocated; + allocated = avo; + _availableOutbound.addAndGet(0 - allocated); + _totalAllocatedOutboundBytes.addAndGet(allocated); req.allocateBytes(0, allocated); satisfied.add(req); if (req.getPendingOutboundRequested() > 0) { @@ -618,6 +662,50 @@ public class FIFOBandwidthLimiter { } } + /** + * Lockless total satisfaction, + * at some minor risk of exceeding the limits + * and driving the available counter below zero + * + * @param requested number of bytes + * @return satisfaction + * @since 0.7.13 + */ + private boolean shortcutSatisfyInboundRequest(int requested) { + boolean rv = _inboundUnlimited || + (_pendingInboundRequests.isEmpty() && + _availableInbound.get() >= requested); + if (rv) { + _availableInbound.addAndGet(0 - requested); + _totalAllocatedInboundBytes.addAndGet(requested); + } + if (_log.shouldLog(Log.INFO)) + _log.info("IB shortcut for " + requested + "B? " + rv); + return rv; + } + + /** + * Lockless total satisfaction, + * at some minor risk of exceeding the limits + * and driving the available counter below zero + * + * @param requested number of bytes + * @return satisfaction + * @since 0.7.13 + */ + private boolean shortcutSatisfyOutboundRequest(int requested) { + boolean rv = _outboundUnlimited || + (_pendingOutboundRequests.isEmpty() && + _availableOutbound.get() >= requested); + if (rv) { + _availableOutbound.addAndGet(0 - requested); + _totalAllocatedOutboundBytes.addAndGet(requested); + } + if (_log.shouldLog(Log.INFO)) + _log.info("OB shortcut for " + requested + "B? " + rv); + return rv; + } + /** @deprecated not worth translating */ public void renderStatusHTML(Writer out) throws IOException { /******* @@ -665,7 +753,7 @@ public class FIFOBandwidthLimiter { private int _allocationsSinceWait; private boolean _aborted; private boolean _waited; - List satisfiedBuffer; + List satisfiedBuffer; private CompleteListener _lsnr; private Object _attachment; @@ -741,7 +829,6 @@ public class FIFOBandwidthLimiter { void allocateAll() { _inAllocated = _inTotal; _outAllocated = _outTotal; - _outAllocated = _outTotal; if (_lsnr == null) _allocationsSinceWait++; if (_log.shouldLog(Log.DEBUG)) _log.debug("allocate all"); @@ -778,6 +865,13 @@ public class FIFOBandwidthLimiter { public String toString() { return getRequestName(); } } + /** + * This is somewhat complicated by having both + * inbound and outbound in a single request. + * Making a request unidirectional would + * be a good simplification. + * But NTCP would have to be changed as it puts them on one queue. + */ public interface Request { /** describe this particular request */ public String getRequestName(); diff --git a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java index cd246bf2c..2065cdc28 100644 --- a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java +++ b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java @@ -71,7 +71,7 @@ public class FIFOBandwidthRefiller implements Runnable { public void run() { // bootstrap 'em with nothing _lastRefillTime = _limiter.now(); - List buffer = new ArrayList(2); + List buffer = new ArrayList(2); while (true) { long now = _limiter.now(); if (now >= _lastCheckConfigTime + _configCheckPeriodMs) { @@ -95,7 +95,7 @@ public class FIFOBandwidthRefiller implements Runnable { _lastCheckConfigTime = _lastRefillTime; } - private boolean updateQueues(List buffer, long now) { + private boolean updateQueues(List buffer, long now) { long numMs = (now - _lastRefillTime); if (_log.shouldLog(Log.INFO)) _log.info("Updating bandwidth after " + numMs + " (status: " + _limiter.getStatus().toString() diff --git a/router/java/src/net/i2p/router/transport/TransportImpl.java b/router/java/src/net/i2p/router/transport/TransportImpl.java index c4bab5132..13c5c5be9 100644 --- a/router/java/src/net/i2p/router/transport/TransportImpl.java +++ b/router/java/src/net/i2p/router/transport/TransportImpl.java @@ -421,8 +421,8 @@ public abstract class TransportImpl implements Transport { } } - /** To protect dev anonymity. Set to true after 0.7.12 is out */ - public static final boolean ADJUST_COST = !RouterVersion.VERSION.equals("0.7.11"); + /** Do we increase the advertised cost when approaching conn limits? */ + public static final boolean ADJUST_COST = true; /** What addresses are we currently listening to? */ public RouterAddress getCurrentAddress() { diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index c584341b9..fdbcc54b4 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -140,7 +140,7 @@ public class TransportManager implements TransportEventListener { configTransports(); _log.debug("Starting up the transport manager"); for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); RouterAddress addr = t.startListening(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Transport " + i + " (" + t.getStyle() + ") started"); @@ -161,14 +161,14 @@ public class TransportManager implements TransportEventListener { if (_upnpManager != null) _upnpManager.stop(); for (int i = 0; i < _transports.size(); i++) { - ((Transport)_transports.get(i)).stopListening(); + _transports.get(i).stopListening(); } _transports.clear(); } public Transport getTransport(String style) { for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); if(style.equals(t.getStyle())) return t; } @@ -189,7 +189,7 @@ public class TransportManager implements TransportEventListener { public int countActivePeers() { int peers = 0; for (int i = 0; i < _transports.size(); i++) { - peers += ((Transport)_transports.get(i)).countActivePeers(); + peers += _transports.get(i).countActivePeers(); } return peers; } @@ -197,7 +197,7 @@ public class TransportManager implements TransportEventListener { public int countActiveSendPeers() { int peers = 0; for (int i = 0; i < _transports.size(); i++) { - peers += ((Transport)_transports.get(i)).countActiveSendPeers(); + peers += _transports.get(i).countActiveSendPeers(); } return peers; } @@ -210,7 +210,7 @@ public class TransportManager implements TransportEventListener { */ public boolean haveOutboundCapacity(int pct) { for (int i = 0; i < _transports.size(); i++) { - if (((Transport)_transports.get(i)).haveCapacity(pct)) + if (_transports.get(i).haveCapacity(pct)) return true; } return false; @@ -225,7 +225,7 @@ public class TransportManager implements TransportEventListener { if (_transports.size() <= 0) return false; for (int i = 0; i < _transports.size(); i++) { - if (!((Transport)_transports.get(i)).haveCapacity(HIGH_CAPACITY_PCT)) + if (!_transports.get(i).haveCapacity(HIGH_CAPACITY_PCT)) return false; } return true; @@ -253,7 +253,7 @@ public class TransportManager implements TransportEventListener { public Vector getClockSkews() { Vector skews = new Vector(); for (int i = 0; i < _transports.size(); i++) { - Vector tempSkews = ((Transport)_transports.get(i)).getClockSkews(); + Vector tempSkews = _transports.get(i).getClockSkews(); if ((tempSkews == null) || (tempSkews.size() <= 0)) continue; skews.addAll(tempSkews); } @@ -275,12 +275,12 @@ public class TransportManager implements TransportEventListener { public void recheckReachability() { for (int i = 0; i < _transports.size(); i++) - ((Transport)_transports.get(i)).recheckReachability(); + _transports.get(i).recheckReachability(); } public boolean isBacklogged(Hash dest) { for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); if (t.isBacklogged(dest)) return true; } @@ -289,7 +289,7 @@ public class TransportManager implements TransportEventListener { public boolean isEstablished(Hash dest) { for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); if (t.isEstablished(dest)) return true; } @@ -303,7 +303,7 @@ public class TransportManager implements TransportEventListener { */ public boolean wasUnreachable(Hash dest) { for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); if (!t.wasUnreachable(dest)) return false; } @@ -371,22 +371,22 @@ public class TransportManager implements TransportEventListener { } public TransportBid getBid(OutNetMessage msg) { - List bids = getBids(msg); + List bids = getBids(msg); if ( (bids == null) || (bids.size() <= 0) ) return null; else - return (TransportBid)bids.get(0); + return bids.get(0); } - public List getBids(OutNetMessage msg) { + public List getBids(OutNetMessage msg) { if (msg == null) throw new IllegalArgumentException("Null message? no bidding on a null outNetMessage!"); if (_context.router().getRouterInfo().equals(msg.getTarget())) throw new IllegalArgumentException("WTF, bids for a message bound to ourselves?"); - List rv = new ArrayList(_transports.size()); + List rv = new ArrayList(_transports.size()); Set failedTransports = msg.getFailedTransports(); for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); if (failedTransports.contains(t.getStyle())) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Skipping transport " + t.getStyle() + " as it already failed"); @@ -415,7 +415,7 @@ public class TransportManager implements TransportEventListener { Set failedTransports = msg.getFailedTransports(); TransportBid rv = null; for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); if (t.isUnreachable(peer)) { unreachableTransports++; // this keeps GetBids() from shitlisting for "no common transports" @@ -482,7 +482,7 @@ public class TransportManager implements TransportEventListener { public List getMostRecentErrorMessages() { List rv = new ArrayList(16); for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); rv.addAll(t.getMostRecentErrorMessages()); } return rv; @@ -491,7 +491,7 @@ public class TransportManager implements TransportEventListener { public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException { TreeMap transports = new TreeMap(); for (int i = 0; i < _transports.size(); i++) { - Transport t = (Transport)_transports.get(i); + Transport t = _transports.get(i); transports.put(t.getStyle(), t); } for (Iterator iter = transports.values().iterator(); iter.hasNext(); ) { @@ -501,7 +501,7 @@ public class TransportManager implements TransportEventListener { StringBuilder buf = new StringBuilder(4*1024); buf.append("

      Router Transport Addresses

      \n");
               for (int i = 0; i < _transports.size(); i++) {
      -            Transport t = (Transport)_transports.get(i);
      +            Transport t = _transports.get(i);
                   if (t.getCurrentAddress() != null)
                       buf.append(t.getCurrentAddress()).append("\n\n");
                   else
      diff --git a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
      index 611665c41..64b745e46 100644
      --- a/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
      +++ b/router/java/src/net/i2p/router/transport/ntcp/EventPumper.java
      @@ -302,7 +302,7 @@ public class EventPumper implements Runnable {
           
           public void wantsWrite(NTCPConnection con, byte data[]) {
               ByteBuffer buf = ByteBuffer.wrap(data);
      -        FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestOutbound(data.length, "NTCP write", null, null);//con, buf);
      +        FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestOutbound(data.length, "NTCP write");//con, buf);
               if (req.getPendingOutboundRequested() > 0) {
                   if (_log.shouldLog(Log.INFO))
                       _log.info("queued write on " + con + " for " + data.length);
      @@ -471,7 +471,7 @@ public class EventPumper implements Runnable {
                       buf.get(data);
                       releaseBuf(buf);
                       ByteBuffer rbuf = ByteBuffer.wrap(data);
      -                FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestInbound(read, "NTCP read", null, null); //con, buf);
      +                FIFOBandwidthLimiter.Request req = _context.bandwidthLimiter().requestInbound(read, "NTCP read"); //con, buf);
                       if (req.getPendingInboundRequested() > 0) {
                           key.interestOps(key.interestOps() & ~SelectionKey.OP_READ);
                           if (_log.shouldLog(Log.DEBUG))
      diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
      index 9eff966bf..043fb18c2 100644
      --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
      +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPConnection.java
      @@ -62,7 +62,7 @@ public class NTCPConnection implements FIFOBandwidthLimiter.CompleteListener {
            * and already cleared through the bandwidth limiter.
            */
           private final LinkedBlockingQueue _writeBufs;
      -    /** Todo: This is only so we can abort() them when we close() ??? */
      +    /** Requests that were not granted immediately */
           private final Set _bwRequests;
           private boolean _established;
           private long _establishedOn;
      diff --git a/router/java/src/net/i2p/router/transport/udp/ACKSender.java b/router/java/src/net/i2p/router/transport/udp/ACKSender.java
      index ec2078312..180232d6f 100644
      --- a/router/java/src/net/i2p/router/transport/udp/ACKSender.java
      +++ b/router/java/src/net/i2p/router/transport/udp/ACKSender.java
      @@ -1,7 +1,10 @@
       package net.i2p.router.transport.udp;
       
      -import java.util.ArrayList;
      +import java.util.HashSet;
       import java.util.List;
      +import java.util.Set;
      +import java.util.concurrent.BlockingQueue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.router.RouterContext;
       import net.i2p.util.I2PThread;
      @@ -18,8 +21,9 @@ public class ACKSender implements Runnable {
           private UDPTransport _transport;
           private PacketBuilder _builder;
           /** list of peers (PeerState) who we have received data from but not yet ACKed to */
      -    private final List _peersToACK;
      +    private final BlockingQueue _peersToACK;
           private boolean _alive;
      +    private static final long POISON_PS = -9999999999l;
           
           /** how frequently do we want to send ACKs to a peer? */
           static final int ACK_FREQUENCY = 500;
      @@ -28,7 +32,7 @@ public class ACKSender implements Runnable {
               _context = ctx;
               _log = ctx.logManager().getLog(ACKSender.class);
               _transport = transport;
      -        _peersToACK = new ArrayList(4);
      +        _peersToACK = new LinkedBlockingQueue();
               _builder = new PacketBuilder(_context, transport);
               _alive = true;
               _context.statManager().createRateStat("udp.sendACKCount", "how many ack messages were sent to a peer", "udp", UDPTransport.RATES);
      @@ -37,27 +41,34 @@ public class ACKSender implements Runnable {
               _context.statManager().createRateStat("udp.abortACK", "How often do we schedule up an ACK send only to find it had already been sent (through piggyback)?", "udp", UDPTransport.RATES);
           }
           
      +    /**
      +     *  Add to the queue.
      +     *  For speed, don't check for duplicates here.
      +     *  The runner will remove them in its own thread.
      +     */
           public void ackPeer(PeerState peer) {
      -        synchronized (_peersToACK) {
      -            if (!_peersToACK.contains(peer))
      -                _peersToACK.add(peer);
      -            _peersToACK.notifyAll();
      -        }
      +        if (_alive)
      +            _peersToACK.offer(peer);
           }
           
           public void startup() {
               _alive = true;
      -        I2PThread t = new I2PThread(this, "UDP ACK sender");
      -        t.setDaemon(true);
      +        _peersToACK.clear();
      +        I2PThread t = new I2PThread(this, "UDP ACK sender", true);
               t.start();
           }
           
           public void shutdown() { 
               _alive = false;
      -        synchronized (_peersToACK) {
      -            _peersToACK.clear();
      -            _peersToACK.notifyAll();
      +        PeerState poison = new PeerState(_context, _transport);
      +        poison.setTheyRelayToUsAs(POISON_PS);
      +        _peersToACK.offer(poison);
      +        for (int i = 1; i <= 5 && !_peersToACK.isEmpty(); i++) {
      +            try {
      +                Thread.sleep(i * 50);
      +            } catch (InterruptedException ie) {}
               }
      +        _peersToACK.clear();
           }
           
           private long ackFrequency(long timeSinceACK, long rtt) {
      @@ -71,47 +82,89 @@ public class ACKSender implements Runnable {
           }
           
           public void run() {
      +
      +        // we use a Set to strip out dups that come in on the Queue
      +        Set notYet = new HashSet();
               while (_alive) {
                   PeerState peer = null;
      -            long now = _context.clock().now();
      +            long now = 0;
                   long remaining = -1;
      -            try {
      -                synchronized (_peersToACK) {
      -                    for (int i = 0; i < _peersToACK.size(); i++) {
      -                        PeerState cur = (PeerState)_peersToACK.get(i);
      -                        long wanted = cur.getWantedACKSendSince();
      -                        long delta = wanted + ackFrequency(now-cur.getLastACKSend(), cur.getRTT()) - now;
      -                        if ( ( (wanted > 0) && (delta < 0) ) || (cur.unsentACKThresholdReached()) ) {
      -                            _peersToACK.remove(i);
      -                            peer = cur;
      -                            break;
      -                        } 
      -                    }
      -                    
      -                    if (peer == null) {
      -                        if (_peersToACK.size() <= 0)
      -                            _peersToACK.wait();
      +            long wanted = 0;
      +
      +                while (_alive) {
      +                    // Pull from the queue until we find one ready to ack
      +                    // Any that are not ready we will put back on the queue
      +                    PeerState cur = null;
      +                    try {
      +                        if (notYet.isEmpty())
      +                            // wait forever
      +                            cur = _peersToACK.take();
                               else
      -                            _peersToACK.wait(50);
      -                    } else {
      -                        remaining = _peersToACK.size();
      -                    }
      -                }
      -            } catch (InterruptedException ie) {}
      -                
      +                            // Don't wait if nothing there, just put everybody back and sleep below
      +                            cur = _peersToACK.poll();
      +                    } catch (InterruptedException ie) {}
      +
      +                    if (cur != null) {
      +                        if (cur.getTheyRelayToUsAs() == POISON_PS)
      +                            return;
      +                        wanted = cur.getWantedACKSendSince();
      +                        now = _context.clock().now();
      +                        long delta = wanted + ackFrequency(now-cur.getLastACKSend(), cur.getRTT()) - now;
      +                        if (wanted <= 0) {
      +                            // it got acked by somebody - discard, remove any dups, and go around again
      +                            notYet.remove(cur);
      +                        } else if ( (delta <= 0) || (cur.unsentACKThresholdReached()) ) {
      +                            // found one to ack
      +                            peer = cur;
      +                            notYet.remove(cur); // in case a dup
      +                            try {
      +                                // bulk operations may throw an exception
      +                                _peersToACK.addAll(notYet);
      +                            } catch (Exception e) {}
      +                            notYet.clear();
      +                            break;
      +                        } else { 
      +                            // not yet, go around again
      +                            // moving from the Queue to the Set and then back removes duplicates
      +                            boolean added = notYet.add(cur);
      +                            if (added && _log.shouldLog(Log.DEBUG))
      +                                _log.debug("Pending ACK (delta = " + delta + ") for " + cur);
      +                        } 
      +                    } else if (!notYet.isEmpty()) {
      +                        // put them all back and wait a while
      +                        try {
      +                            // bulk operations may throw an exception
      +                            _peersToACK.addAll(notYet);
      +                        } catch (Exception e) {}
      +                        if (_log.shouldLog(Log.INFO))
      +                            _log.info("sleeping, pending size = " + notYet.size());
      +                        notYet.clear();
      +                        try {
      +                            // sleep a little longer than the divided frequency,
      +                            // so it will be ready after we circle around a few times
      +                            Thread.sleep(5 + (ACK_FREQUENCY / 3));
      +                        } catch (InterruptedException ie) {}
      +                    } // else go around again where we will wait at take()
      +                } // inner while()
      +                    
                   if (peer != null) {
                       long lastSend = peer.getLastACKSend();
      -                long wanted = peer.getWantedACKSendSince();
      -                List ackBitfields = peer.retrieveACKBitfields(false);
      +                // set above before the break
      +                //long wanted = peer.getWantedACKSendSince();
      +                List ackBitfields = peer.retrieveACKBitfields(false);
                       
      -                if (wanted < 0)
      -                    _log.error("wtf, why are we acking something they dont want?  remaining=" + remaining + ", peer=" + peer + ", bitfields=" + ackBitfields);
      +                if (wanted < 0) {
      +                    if (_log.shouldLog(Log.WARN))
      +                        _log.warn("wtf, why are we acking something they dont want?  remaining=" + remaining + ", peer=" + peer + ", bitfields=" + ackBitfields);
      +                    continue;
      +                }
                       
      -                if ( (ackBitfields != null) && (ackBitfields.size() > 0) ) {
      +                if ( (ackBitfields != null) && (!ackBitfields.isEmpty()) ) {
                           _context.statManager().addRateData("udp.sendACKCount", ackBitfields.size(), 0);
                           if (remaining > 0)
                               _context.statManager().addRateData("udp.sendACKRemaining", remaining, 0);
      -                    now = _context.clock().now();
      +                    // set above before the break
      +                    //now = _context.clock().now();
                           if (lastSend < 0)
                               lastSend = now - 1;
                           _context.statManager().addRateData("udp.ackFrequency", now-lastSend, now-wanted);
      @@ -119,7 +172,7 @@ public class ACKSender implements Runnable {
                           UDPPacket ack = _builder.buildACK(peer, ackBitfields);
                           ack.markType(1);
                           ack.setFragmentCount(-1);
      -                    ack.setMessageType(42);
      +                    ack.setMessageType(PacketBuilder.TYPE_ACK);
                           
                           if (_log.shouldLog(Log.INFO))
                               _log.info("Sending ACK for " + ackBitfields);
      diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
      index 3b5938918..91c23b444 100644
      --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
      +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
      @@ -7,6 +7,7 @@ import java.util.HashMap;
       import java.util.Iterator;
       import java.util.List;
       import java.util.Map;
      +import java.util.concurrent.ConcurrentHashMap;
       
       import net.i2p.crypto.DHSessionKeyBuilder;
       import net.i2p.data.Base64;
      @@ -37,13 +38,13 @@ public class EstablishmentManager {
           private UDPTransport _transport;
           private PacketBuilder _builder;
           /** map of RemoteHostId to InboundEstablishState */
      -    private final Map _inboundStates;
      +    private final ConcurrentHashMap _inboundStates;
           /** map of RemoteHostId to OutboundEstablishState */
      -    private final Map _outboundStates;
      +    private final ConcurrentHashMap _outboundStates;
           /** map of RemoteHostId to List of OutNetMessage for messages exceeding capacity */
      -    private final Map _queuedOutbound;
      +    private final ConcurrentHashMap> _queuedOutbound;
           /** map of nonce (Long) to OutboundEstablishState */
      -    private final Map _liveIntroductions;
      +    private final ConcurrentHashMap _liveIntroductions;
           private boolean _alive;
           private final Object _activityLock;
           private int _activity;
      @@ -56,10 +57,10 @@ public class EstablishmentManager {
               _log = ctx.logManager().getLog(EstablishmentManager.class);
               _transport = transport;
               _builder = new PacketBuilder(ctx, transport);
      -        _inboundStates = new HashMap(32);
      -        _outboundStates = new HashMap(32);
      -        _queuedOutbound = new HashMap(32);
      -        _liveIntroductions = new HashMap(32);
      +        _inboundStates = new ConcurrentHashMap();
      +        _outboundStates = new ConcurrentHashMap();
      +        _queuedOutbound = new ConcurrentHashMap();
      +        _liveIntroductions = new ConcurrentHashMap();
               _activityLock = new Object();
               _context.statManager().createRateStat("udp.inboundEstablishTime", "How long it takes for a new inbound session to be established", "udp", UDPTransport.RATES);
               _context.statManager().createRateStat("udp.outboundEstablishTime", "How long it takes for a new outbound session to be established", "udp", UDPTransport.RATES);
      @@ -74,8 +75,7 @@ public class EstablishmentManager {
           
           public void startup() {
               _alive = true;
      -        I2PThread t = new I2PThread(new Establisher(), "UDP Establisher");
      -        t.setDaemon(true);
      +        I2PThread t = new I2PThread(new Establisher(), "UDP Establisher", true);
               t.start();
           }
           public void shutdown() { 
      @@ -87,21 +87,17 @@ public class EstablishmentManager {
            * Grab the active establishing state
            */
           InboundEstablishState getInboundState(RemoteHostId from) {
      -        synchronized (_inboundStates) {
      -            InboundEstablishState state = (InboundEstablishState)_inboundStates.get(from);
      +            InboundEstablishState state = _inboundStates.get(from);
                   // if ( (state == null) && (_log.shouldLog(Log.DEBUG)) )
                   //     _log.debug("No inbound states for " + from + ", with remaining: " + _inboundStates);
                   return state;
      -        }
           }
           
           OutboundEstablishState getOutboundState(RemoteHostId from) {
      -        synchronized (_outboundStates) {
      -            OutboundEstablishState state = (OutboundEstablishState)_outboundStates.get(from);
      +            OutboundEstablishState state = _outboundStates.get(from);
                   // if ( (state == null) && (_log.shouldLog(Log.DEBUG)) )
                   //     _log.debug("No outbound states for " + from + ", with remaining: " + _outboundStates);
                   return state;
      -        }
           }
           
           private int getMaxConcurrentEstablish() {
      @@ -163,39 +159,42 @@ public class EstablishmentManager {
               int deferred = 0;
               boolean rejected = false;
               int queueCount = 0;
      -        synchronized (_outboundStates) {
      -            state = (OutboundEstablishState)_outboundStates.get(to);
      +
      +            state = _outboundStates.get(to);
                   if (state == null) {
                       if (_outboundStates.size() >= getMaxConcurrentEstablish()) {
      -                    List queued = (List)_queuedOutbound.get(to);
      -                    if (queued == null) {
      -                        queued = new ArrayList(1);
      -                        if (_queuedOutbound.size() > MAX_QUEUED_OUTBOUND) {
      -                            rejected = true;
      -                        } else {
      -                            _queuedOutbound.put(to, queued);
      -                        }
      +                    if (_queuedOutbound.size() > MAX_QUEUED_OUTBOUND) {
      +                        rejected = true;
      +                    } else {
      +                        List newQueued = new ArrayList(1);
      +                        List queued = _queuedOutbound.putIfAbsent(to, newQueued);
      +                        if (queued == null)
      +                            queued = newQueued;
      +                        queueCount = queued.size();
      +                        if (queueCount < MAX_QUEUED_PER_PEER)
      +                            queued.add(msg);
                           }
      -                    queueCount = queued.size();
      -                    if ( (queueCount < MAX_QUEUED_PER_PEER) && (!rejected) )
      -                        queued.add(msg);
                           deferred = _queuedOutbound.size();
                       } else {
                           state = new OutboundEstablishState(_context, remAddr, port, 
                                                              msg.getTarget().getIdentity(), 
                                                              new SessionKey(addr.getIntroKey()), addr);
      -                    _outboundStates.put(to, state);
      -                    SimpleScheduler.getInstance().addEvent(new Expire(to, state), 10*1000);
      +                    OutboundEstablishState oldState = _outboundStates.putIfAbsent(to, state);
      +                    boolean isNew = oldState == null;
      +                    if (!isNew)
      +                        // whoops, somebody beat us to it, throw out the state we just created
      +                        state = oldState;
      +                    else
      +                        SimpleScheduler.getInstance().addEvent(new Expire(to, state), 10*1000);
                       }
                   }
                   if (state != null) {
                       state.addMessage(msg);
      -                List queued = (List)_queuedOutbound.remove(to);
      +                List queued = _queuedOutbound.remove(to);
                       if (queued != null)
                           for (int i = 0; i < queued.size(); i++)
      -                        state.addMessage((OutNetMessage)queued.get(i));
      +                        state.addMessage(queued.get(i));
                   }
      -        }
               
               if (rejected) {
                   _transport.failed(msg, "Too many pending outbound connections");
      @@ -223,17 +222,9 @@ public class EstablishmentManager {
                   _state = state; 
               }
               public void timeReached() {
      -            Object removed = null;
      -            synchronized (_outboundStates) {
      -                removed = _outboundStates.remove(_to);
      -                if ( (removed != null) && (removed != _state) ) { // oops, we must have failed, then retried
      -                    _outboundStates.put(_to, removed);
      -                    removed = null;
      -                }/* else {
      -                    locked_admitQueued();
      -                }*/
      -            }
      -            if (removed != null) {
      +            // remove only if value == state
      +            boolean removed = _outboundStates.remove(_to, _state);
      +            if (removed) {
                       _context.statManager().addRateData("udp.outboundEstablishFailedState", _state.getState(), _state.getLifetime());
                       if (_log.shouldLog(Log.WARN))
                           _log.warn("Timing out expired outbound: " + _state);
      @@ -260,12 +251,11 @@ public class EstablishmentManager {
               int maxInbound = getMaxInboundEstablishers();
               
               boolean isNew = false;
      -        InboundEstablishState state = null;
      -        synchronized (_inboundStates) {
      +
                   if (_inboundStates.size() >= maxInbound)
                       return; // drop the packet
                   
      -            state = (InboundEstablishState)_inboundStates.get(from);
      +            InboundEstablishState state = _inboundStates.get(from);
                   if (state == null) {
                       if (_context.blocklist().isBlocklisted(from.getIP())) {
                           if (_log.shouldLog(Log.WARN))
      @@ -276,10 +266,13 @@ public class EstablishmentManager {
                           return; // drop the packet
                       state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getLocalPort());
                       state.receiveSessionRequest(reader.getSessionRequestReader());
      -                isNew = true;
      -                _inboundStates.put(from, state);
      +                InboundEstablishState oldState = _inboundStates.putIfAbsent(from, state);
      +                isNew = oldState == null;
      +                if (!isNew)
      +                    // whoops, somebody beat us to it, throw out the state we just created
      +                    state = oldState;
                   }
      -        }
      +
               if (isNew) {
                   // we don't expect inbound connections when hidden, but it could happen
                   // Don't offer if we are approaching max connections. While Relay Intros do not
      @@ -307,10 +300,7 @@ public class EstablishmentManager {
            * establishment) 
            */
           void receiveSessionConfirmed(RemoteHostId from, UDPPacketReader reader) {
      -        InboundEstablishState state = null;
      -        synchronized (_inboundStates) {
      -            state = (InboundEstablishState)_inboundStates.get(from);
      -        }
      +        InboundEstablishState state = _inboundStates.get(from);
               if (state != null) {
                   state.receiveSessionConfirmed(reader.getSessionConfirmedReader());
                   notifyActivity();
      @@ -324,10 +314,7 @@ public class EstablishmentManager {
            *
            */
           void receiveSessionCreated(RemoteHostId from, UDPPacketReader reader) {
      -        OutboundEstablishState state = null;
      -        synchronized (_outboundStates) {
      -            state = (OutboundEstablishState)_outboundStates.get(from);
      -        }
      +        OutboundEstablishState state = _outboundStates.get(from);
               if (state != null) {
                   state.receiveSessionCreated(reader.getSessionCreatedReader());
                   notifyActivity();
      @@ -346,21 +333,19 @@ public class EstablishmentManager {
               //int active = 0;
               //int admitted = 0;
               //int remaining = 0;
      -        synchronized (_outboundStates) {
      +
                   //active = _outboundStates.size();
                   _outboundStates.remove(state.getRemoteHostId());
      -            if (_queuedOutbound.size() > 0) {
                       // there shouldn't have been queued messages for this active state, but just in case...
      -                List queued = (List)_queuedOutbound.remove(state.getRemoteHostId());
      +                List queued = _queuedOutbound.remove(state.getRemoteHostId());
                       if (queued != null) {
                           for (int i = 0; i < queued.size(); i++) 
      -                        state.addMessage((OutNetMessage)queued.get(i));
      +                        state.addMessage(queued.get(i));
                       }
                       
                       //admitted = locked_admitQueued();
      -            }
                   //remaining = _queuedOutbound.size();
      -        }
      +
               //if (admitted > 0)
               //    _log.log(Log.CRIT, "Admitted " + admitted + " with " + remaining + " remaining queued and " + active + " active");
               
      @@ -371,6 +356,7 @@ public class EstablishmentManager {
               return peer;
           }
       
      +/********
           private int locked_admitQueued() {
               int admitted = 0;
               while ( (_queuedOutbound.size() > 0) && (_outboundStates.size() < getMaxConcurrentEstablish()) ) {
      @@ -409,6 +395,7 @@ public class EstablishmentManager {
               }
               return admitted;
           }
      +*******/
           
           private void notifyActivity() {
               synchronized (_activityLock) { 
      @@ -596,9 +583,7 @@ public class EstablishmentManager {
               } catch (DHSessionKeyBuilder.InvalidPublicParameterException ippe) {
                   if (_log.shouldLog(Log.ERROR))
                       _log.error("Peer " + state.getRemoteHostId() + " sent us an invalid DH parameter (or were spoofed)", ippe);
      -            synchronized (_inboundStates) {
      -                _inboundStates.remove(state.getRemoteHostId());
      -            }
      +            _inboundStates.remove(state.getRemoteHostId());
                   return;
               }
               _transport.send(_builder.buildSessionCreatedPacket(state, _transport.getExternalPort(), _transport.getIntroKey()));
      @@ -627,14 +612,12 @@ public class EstablishmentManager {
           private void handlePendingIntro(OutboundEstablishState state) {
               long nonce = _context.random().nextLong(MAX_NONCE);
               while (true) {
      -            synchronized (_liveIntroductions) {
      -                OutboundEstablishState old = (OutboundEstablishState)_liveIntroductions.put(new Long(nonce), state);
      +                OutboundEstablishState old = _liveIntroductions.putIfAbsent(new Long(nonce), state);
                       if (old != null) {
                           nonce = _context.random().nextLong(MAX_NONCE);
                       } else {
                           break;
                       }
      -            }
               }
               SimpleScheduler.getInstance().addEvent(new FailIntroduction(state, nonce), INTRO_ATTEMPT_TIMEOUT);
               state.setIntroNonce(nonce);
      @@ -656,16 +639,9 @@ public class EstablishmentManager {
                   _state = state;
               }
               public void timeReached() {
      -            OutboundEstablishState removed = null;
      -            synchronized (_liveIntroductions) {
      -                removed = (OutboundEstablishState)_liveIntroductions.remove(new Long(_nonce));
      -                if (removed != _state) {
      -                    // another one with the same nonce in a very brief time...
      -                    _liveIntroductions.put(new Long(_nonce), removed);
      -                    removed = null;
      -                }
      -            }
      -            if (removed != null) {
      +            // remove only if value equal to state
      +            boolean removed = _liveIntroductions.remove(new Long(_nonce), _state);
      +            if (removed) {
                       if (_log.shouldLog(Log.DEBUG))
                           _log.debug("Send intro for " + _state.getRemoteHostId().toString() + " timed out");
                       _context.statManager().addRateData("udp.sendIntroRelayTimeout", 1, 0);
      @@ -677,10 +653,7 @@ public class EstablishmentManager {
           /* FIXME Exporting non-public type through public API FIXME */
           public void receiveRelayResponse(RemoteHostId bob, UDPPacketReader reader) {
               long nonce = reader.getRelayResponseReader().readNonce();
      -        OutboundEstablishState state = null;
      -        synchronized (_liveIntroductions) {
      -            state = (OutboundEstablishState)_liveIntroductions.remove(new Long(nonce));
      -        }
      +        OutboundEstablishState state = _liveIntroductions.remove(new Long(nonce));
               if (state == null) 
                   return; // already established
               
      @@ -705,10 +678,8 @@ public class EstablishmentManager {
                             + addr.toString() + ":" + port + " (according to " + bob.toString(true) + ")");
               RemoteHostId oldId = state.getRemoteHostId();
               state.introduced(addr, ip, port);
      -        synchronized (_outboundStates) {
      -            _outboundStates.remove(oldId);
      -            _outboundStates.put(state.getRemoteHostId(), state);
      -        }
      +        _outboundStates.remove(oldId);
      +        _outboundStates.put(state.getRemoteHostId(), state);
               notifyActivity();
           }
           
      @@ -748,11 +719,11 @@ public class EstablishmentManager {
               long now = _context.clock().now();
               long nextSendTime = -1;
               InboundEstablishState inboundState = null;
      -        synchronized (_inboundStates) {
      +
                   //if (_log.shouldLog(Log.DEBUG))
                   //    _log.debug("# inbound states: " + _inboundStates.size());
      -            for (Iterator iter = _inboundStates.values().iterator(); iter.hasNext(); ) {
      -                InboundEstablishState cur = (InboundEstablishState)iter.next();
      +            for (Iterator iter = _inboundStates.values().iterator(); iter.hasNext(); ) {
      +                InboundEstablishState cur = iter.next();
                       if (cur.getState() == InboundEstablishState.STATE_CONFIRMED_COMPLETELY) {
                           // completely received (though the signature may be invalid)
                           iter.remove();
      @@ -791,7 +762,6 @@ public class EstablishmentManager {
                           }
                       }
                   }
      -        }
       
               if (inboundState != null) {
                   //if (_log.shouldLog(Log.DEBUG))
      @@ -853,12 +823,12 @@ public class EstablishmentManager {
               //int admitted = 0;
               //int remaining = 0;
               //int active = 0;
      -        synchronized (_outboundStates) {
      +
                   //active = _outboundStates.size();
                   //if (_log.shouldLog(Log.DEBUG))
                   //    _log.debug("# outbound states: " + _outboundStates.size());
      -            for (Iterator iter = _outboundStates.values().iterator(); iter.hasNext(); ) {
      -                OutboundEstablishState cur = (OutboundEstablishState)iter.next();
      +            for (Iterator iter = _outboundStates.values().iterator(); iter.hasNext(); ) {
      +                OutboundEstablishState cur = iter.next();
                       if (cur == null) continue;
                       if (cur.getState() == OutboundEstablishState.STATE_CONFIRMED_COMPLETELY) {
                           // completely received
      @@ -902,7 +872,6 @@ public class EstablishmentManager {
                   
                   //admitted = locked_admitQueued();    
                   //remaining = _queuedOutbound.size();
      -        }
       
               //if (admitted > 0)
               //    _log.log(Log.CRIT, "Admitted " + admitted + " in push with " + remaining + " remaining queued and " + active + " active");
      diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
      index 46085e3b4..829f62ee0 100644
      --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
      +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java
      @@ -21,17 +21,17 @@ import net.i2p.util.Log;
        *
        */
       public class InboundEstablishState {
      -    private RouterContext _context;
      -    private Log _log;
      +    private final RouterContext _context;
      +    private final Log _log;
           // SessionRequest message
           private byte _receivedX[];
           private byte _bobIP[];
      -    private int _bobPort;
      +    private final int _bobPort;
           private DHSessionKeyBuilder _keyBuilder;
           // SessionCreated message
           private byte _sentY[];
      -    private byte _aliceIP[];
      -    private int _alicePort;
      +    private final byte _aliceIP[];
      +    private final int _alicePort;
           private long _sentRelayTag;
           private long _sentSignedOnTime;
           private SessionKey _sessionKey;
      @@ -44,11 +44,11 @@ public class InboundEstablishState {
           private boolean _verificationAttempted;
           private RouterIdentity _receivedConfirmedIdentity;
           // general status 
      -    private long _establishBegin;
      -    private long _lastReceive;
      +    private final long _establishBegin;
      +    //private long _lastReceive;
           // private long _lastSend;
           private long _nextSend;
      -    private RemoteHostId _remoteHostId;
      +    private final RemoteHostId _remoteHostId;
           private int _currentState;
           private boolean _complete;
           
      @@ -121,9 +121,10 @@ public class InboundEstablishState {
           public synchronized SessionKey getMACKey() { return _macKey; }
       
           /** what IP do they appear to be on? */
      -    public synchronized byte[] getSentIP() { return _aliceIP; }
      +    public byte[] getSentIP() { return _aliceIP; }
      +
           /** what port number do they appear to be coming from? */
      -    public synchronized int getSentPort() { return _alicePort; }
      +    public int getSentPort() { return _alicePort; }
           
           public synchronized byte[] getBobIP() { return _bobIP; }
           
      @@ -205,8 +206,8 @@ public class InboundEstablishState {
           }
           
           /** how long have we been trying to establish this session? */
      -    public synchronized long getLifetime() { return _context.clock().now() - _establishBegin; }
      -    public synchronized long getEstablishBeginTime() { return _establishBegin; }
      +    public long getLifetime() { return _context.clock().now() - _establishBegin; }
      +    public long getEstablishBeginTime() { return _establishBegin; }
           public synchronized long getNextSendTime() { return _nextSend; }
           public synchronized void setNextSendTime(long when) { _nextSend = when; }
       
      @@ -328,8 +329,7 @@ public class InboundEstablishState {
           }
           
           private void packetReceived() {
      -        _lastReceive = _context.clock().now();
      -        _nextSend = _lastReceive;
      +        _nextSend = _context.clock().now();
           }
           
           @Override
      diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java
      index 04c2a3184..ddd44b143 100644
      --- a/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java
      +++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageFragments.java
      @@ -96,8 +96,8 @@ public class InboundMessageFragments /*implements UDPTransport.PartialACKSource
               if (fragments <= 0) return fragments;
               Hash fromPeer = from.getRemotePeer();
                   
      -        Map messages = from.getInboundMessages();
      -            
      +        Map messages = from.getInboundMessages();
      +
               for (int i = 0; i < fragments; i++) {
                   long mid = data.readMessageId(i);
                   Long messageId = new Long(mid);
      @@ -122,7 +122,7 @@ public class InboundMessageFragments /*implements UDPTransport.PartialACKSource
                   boolean partialACK = false;
                
                   synchronized (messages) {
      -                state = (InboundMessageState)messages.get(messageId);
      +                state = messages.get(messageId);
                       if (state == null) {
                           state = new InboundMessageState(_context, mid, fromPeer);
                           messages.put(messageId, state);
      diff --git a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
      index 6a1229579..3159cad87 100644
      --- a/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
      +++ b/router/java/src/net/i2p/router/transport/udp/InboundMessageState.java
      @@ -172,8 +172,8 @@ public class InboundMessageState {
           
           @Override
           public String toString() {
      -        StringBuilder buf = new StringBuilder(32);
      -        buf.append("Message: ").append(_messageId);
      +        StringBuilder buf = new StringBuilder(256);
      +        buf.append("IB Message: ").append(_messageId);
               if (isComplete()) {
                   buf.append(" completely received with ");
                   buf.append(getCompleteSize()).append(" bytes");
      diff --git a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java
      index 0fe2f96a0..6a2707f60 100644
      --- a/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java
      +++ b/router/java/src/net/i2p/router/transport/udp/IntroductionManager.java
      @@ -6,12 +6,15 @@ import java.util.HashMap;
       import java.util.List;
       import java.util.Map;
       import java.util.Properties;
      +import java.util.Set;
      +import java.util.concurrent.ConcurrentHashMap;
       
       import net.i2p.data.Base64;
       import net.i2p.data.RouterAddress;
       import net.i2p.data.RouterInfo;
       import net.i2p.data.SessionKey;
       import net.i2p.router.RouterContext;
      +import net.i2p.util.ConcurrentHashSet;
       import net.i2p.util.Log;
       
       /**
      @@ -23,17 +26,17 @@ public class IntroductionManager {
           private UDPTransport _transport;
           private PacketBuilder _builder;
           /** map of relay tag to PeerState that should receive the introduction */
      -    private Map _outbound;
      +    private final Map _outbound;
           /** list of peers (PeerState) who have given us introduction tags */
      -    private final List _inbound;
      +    private final Set _inbound;
       
           public IntroductionManager(RouterContext ctx, UDPTransport transport) {
               _context = ctx;
               _log = ctx.logManager().getLog(IntroductionManager.class);
               _transport = transport;
               _builder = new PacketBuilder(ctx, transport);
      -        _outbound = Collections.synchronizedMap(new HashMap(128));
      -        _inbound = new ArrayList(128);
      +        _outbound = new ConcurrentHashMap(128);
      +        _inbound = new ConcurrentHashSet(128);
               ctx.statManager().createRateStat("udp.receiveRelayIntro", "How often we get a relayed request for us to talk to someone?", "udp", UDPTransport.RATES);
               ctx.statManager().createRateStat("udp.receiveRelayRequest", "How often we receive a good request to relay to someone else?", "udp", UDPTransport.RATES);
               ctx.statManager().createRateStat("udp.receiveRelayRequestBadTag", "Received relay requests with bad/expired tag", "udp", UDPTransport.RATES);
      @@ -52,10 +55,7 @@ public class IntroductionManager {
               if (peer.getWeRelayToThemAs() > 0) 
                   _outbound.put(new Long(peer.getWeRelayToThemAs()), peer);
               if (peer.getTheyRelayToUsAs() > 0) {
      -            synchronized (_inbound) {
      -                if (!_inbound.contains(peer))
                           _inbound.add(peer);
      -            }
               }
           }
           
      @@ -67,9 +67,7 @@ public class IntroductionManager {
               if (peer.getWeRelayToThemAs() > 0) 
                   _outbound.remove(new Long(peer.getWeRelayToThemAs()));
               if (peer.getTheyRelayToUsAs() > 0) {
      -            synchronized (_inbound) {
      -                _inbound.remove(peer);
      -            }
      +            _inbound.remove(peer);
               }
           }
           
      @@ -90,14 +88,11 @@ public class IntroductionManager {
            * and we want to keep our introducers valid.
            */
           public int pickInbound(Properties ssuOptions, int howMany) {
      -        List peers = null;
               int start = _context.random().nextInt(Integer.MAX_VALUE);
      -        synchronized (_inbound) {
      -            if (_log.shouldLog(Log.DEBUG))
      -                _log.debug("Picking inbound out of " + _inbound.size());
      -            if (_inbound.size() <= 0) return 0;
      -            peers = new ArrayList(_inbound);
      -        }
      +        if (_log.shouldLog(Log.DEBUG))
      +            _log.debug("Picking inbound out of " + _inbound.size());
      +        if (_inbound.isEmpty()) return 0;
      +        List peers = new ArrayList(_inbound);
               int sz = peers.size();
               start = start % sz;
               int found = 0;
      @@ -164,9 +159,7 @@ public class IntroductionManager {
            * @return number of peers that have volunteerd to introduce us
            */
           int introducerCount() {
      -        synchronized(_inbound) {
                   return _inbound.size();
      -        }
           }
       
           void receiveRelayIntro(RemoteHostId bob, UDPPacketReader reader) {
      diff --git a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java
      index d7f0421e3..39b4163e7 100644
      --- a/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java
      +++ b/router/java/src/net/i2p/router/transport/udp/MessageReceiver.java
      @@ -1,7 +1,7 @@
       package net.i2p.router.transport.udp;
       
      -import java.util.ArrayList;
      -import java.util.List;
      +import java.util.concurrent.BlockingQueue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.data.Base64;
       import net.i2p.data.ByteArray;
      @@ -24,15 +24,17 @@ public class MessageReceiver {
           private Log _log;
           private UDPTransport _transport;
           /** list of messages (InboundMessageState) fully received but not interpreted yet */
      -    private final List _completeMessages;
      +    private final BlockingQueue _completeMessages;
           private boolean _alive;
           private ByteCache _cache;
      +    private static final int THREADS = 5;
      +    private static final long POISON_IMS = -99999999999l;
           
           public MessageReceiver(RouterContext ctx, UDPTransport transport) {
               _context = ctx;
               _log = ctx.logManager().getLog(MessageReceiver.class);
               _transport = transport;
      -        _completeMessages = new ArrayList(16);
      +        _completeMessages = new LinkedBlockingQueue();
               _cache = ByteCache.getInstance(64, I2NPMessage.MAX_SIZE);
               _context.statManager().createRateStat("udp.inboundExpired", "How many messages were expired before reception?", "udp", UDPTransport.RATES);
               _context.statManager().createRateStat("udp.inboundRemaining", "How many messages were remaining when a message is pulled off the complete queue?", "udp", UDPTransport.RATES);
      @@ -46,9 +48,8 @@ public class MessageReceiver {
           
           public void startup() {
               _alive = true;
      -        for (int i = 0; i < 5; i++) {
      -            I2PThread t = new I2PThread(new Runner(), "UDP message receiver " + i);
      -            t.setDaemon(true);
      +        for (int i = 0; i < THREADS; i++) {
      +            I2PThread t = new I2PThread(new Runner(), "UDP message receiver " + i + '/' + THREADS, true);
                   t.start();
               }
           }
      @@ -61,26 +62,31 @@ public class MessageReceiver {
           
           public void shutdown() {
               _alive = false;
      -        synchronized (_completeMessages) {
      -            _completeMessages.clear();
      -            _completeMessages.notifyAll();
      +        _completeMessages.clear();
      +        for (int i = 0; i < THREADS; i++) {
      +            InboundMessageState ims = new InboundMessageState(_context, POISON_IMS, null);
      +            _completeMessages.offer(ims);
               }
      +        for (int i = 1; i <= 5 && !_completeMessages.isEmpty(); i++) {
      +            try {
      +                Thread.sleep(i * 50);
      +            } catch (InterruptedException ie) {}
      +        }
      +        _completeMessages.clear();
           }
           
           public void receiveMessage(InboundMessageState state) {
      -        int total = 0;
      -        long lag = -1;
      -        synchronized (_completeMessages) {
      -            _completeMessages.add(state);
      -            total = _completeMessages.size();
      -            if (total > 1)
      -                lag = ((InboundMessageState)_completeMessages.get(0)).getLifetime();
      -            _completeMessages.notifyAll();
      -        }
      -        if (total > 1)
      -            _context.statManager().addRateData("udp.inboundReady", total, 0);
      -        if (lag > 1000)
      -            _context.statManager().addRateData("udp.inboundLag", lag, total);
      +        //int total = 0;
      +        //long lag = -1;
      +        if (_alive)
      +            _completeMessages.offer(state);
      +        //total = _completeMessages.size();
      +        //if (total > 1)
      +        //    lag = ((InboundMessageState)_completeMessages.get(0)).getLifetime();
      +        //if (total > 1)
      +        //    _context.statManager().addRateData("udp.inboundReady", total, 0);
      +        //if (lag > 1000)
      +        //    _context.statManager().addRateData("udp.inboundLag", lag, total);
           }
           
           public void loop(I2NPMessageHandler handler) {
      @@ -91,19 +97,18 @@ public class MessageReceiver {
                   long expiredLifetime = 0;
                   int remaining = 0;
                   try {
      -                synchronized (_completeMessages) {
                           while (message == null) {
      -                        if (_completeMessages.size() > 0) // grab the tail for lowest latency
      -                            message = (InboundMessageState)_completeMessages.remove(_completeMessages.size()-1);
      -                        else
      -                            _completeMessages.wait(5000);
      +                        message = _completeMessages.take();
      +                        if ( (message != null) && (message.getMessageId() == POISON_IMS) ) {
      +                            message = null;
      +                            break;
      +                        }
                               if ( (message != null) && (message.isExpired()) ) {
                                   expiredLifetime += message.getLifetime();
                                   message = null;
                                   expired++;
                               }
      -                        remaining = _completeMessages.size();
      -                    }
      +                        //remaining = _completeMessages.size();
                       }
                   } catch (InterruptedException ie) {}
                   
      diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
      index ece42da7a..fa01bc026 100644
      --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
      +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java
      @@ -1,8 +1,8 @@
       package net.i2p.router.transport.udp;
       
       import java.net.InetAddress;
      -import java.util.ArrayList;
      -import java.util.List;
      +import java.util.Queue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.crypto.DHSessionKeyBuilder;
       import net.i2p.data.Base64;
      @@ -22,8 +22,8 @@ import net.i2p.util.Log;
        *
        */
       public class OutboundEstablishState {
      -    private RouterContext _context;
      -    private Log _log;
      +    private final RouterContext _context;
      +    private final Log _log;
           // SessionRequest message
           private byte _sentX[];
           private byte _bobIP[];
      @@ -44,18 +44,18 @@ public class OutboundEstablishState {
           private long _sentSignedOnTime;
           private Signature _sentSignature;
           // general status 
      -    private long _establishBegin;
      -    private long _lastReceive;
      +    private final long _establishBegin;
      +    //private long _lastReceive;
           private long _lastSend;
           private long _nextSend;
           private RemoteHostId _remoteHostId;
      -    private RouterIdentity _remotePeer;
      +    private final RouterIdentity _remotePeer;
           private SessionKey _introKey;
      -    private final List _queuedMessages;
      +    private final Queue _queuedMessages;
           private int _currentState;
           private long _introductionNonce;
           // intro
      -    private UDPAddress _remoteAddress;
      +    private final UDPAddress _remoteAddress;
           private boolean _complete;
           
           /** nothin sent yet */
      @@ -87,7 +87,7 @@ public class OutboundEstablishState {
               _remotePeer = remotePeer;
               _introKey = introKey;
               _keyBuilder = null;
      -        _queuedMessages = new ArrayList(4);
      +        _queuedMessages = new LinkedBlockingQueue();
               _currentState = STATE_UNKNOWN;
               _establishBegin = ctx.clock().now();
               _remoteAddress = addr;
      @@ -113,22 +113,21 @@ public class OutboundEstablishState {
           public long getIntroNonce() { return _introductionNonce; }
           
           public void addMessage(OutNetMessage msg) {
      -        synchronized (_queuedMessages) {
      -            if (!_queuedMessages.contains(msg))
      -                _queuedMessages.add(msg);
      -        }
      +        // chance of a duplicate here in a race, that's ok
      +        if (!_queuedMessages.contains(msg))
      +            _queuedMessages.offer(msg);
      +        else if (_log.shouldLog(Log.WARN))
      +             _log.warn("attempt to add duplicate msg to queue: " + msg);
           }
      +
           public OutNetMessage getNextQueuedMessage() { 
      -        synchronized (_queuedMessages) {
      -            if (_queuedMessages.size() > 0)
      -                return (OutNetMessage)_queuedMessages.remove(0);
      -        }
      -        return null;
      +        return _queuedMessages.poll();
           }
           
           public RouterIdentity getRemoteIdentity() { return _remotePeer; }
           public SessionKey getIntroKey() { return _introKey; }
           
      +    /** called from constructor, no need to synch */
           private void prepareSessionRequest() {
               _keyBuilder = new DHSessionKeyBuilder();
               byte X[] = _keyBuilder.getMyPublicValue().toByteArray();
      @@ -142,7 +141,7 @@ public class OutboundEstablishState {
                   System.arraycopy(X, 0, _sentX, _sentX.length - X.length, X.length);
           }
       
      -    public synchronized byte[] getSentX() { return _sentX; }
      +    public byte[] getSentX() { return _sentX; }
           public synchronized byte[] getSentIP() { return _bobIP; }
           public synchronized int getSentPort() { return _bobPort; }
       
      @@ -403,8 +402,8 @@ public class OutboundEstablishState {
           }
           
           /** how long have we been trying to establish this session? */
      -    public synchronized long getLifetime() { return _context.clock().now() - _establishBegin; }
      -    public synchronized long getEstablishBeginTime() { return _establishBegin; }
      +    public long getLifetime() { return _context.clock().now() - _establishBegin; }
      +    public long getEstablishBeginTime() { return _establishBegin; }
           public synchronized long getNextSendTime() { return _nextSend; }
           public synchronized void setNextSendTime(long when) { 
               _nextSend = when; 
      @@ -422,8 +421,7 @@ public class OutboundEstablishState {
           }
           
           private void packetReceived() {
      -        _lastReceive = _context.clock().now();
      -        _nextSend = _lastReceive;
      +        _nextSend = _context.clock().now();
               if (_log.shouldLog(Log.DEBUG))
                   _log.debug("Got a packet, nextSend == now");
           }
      diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java
      index f1d10e122..8c1f6904b 100644
      --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java
      +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java
      @@ -28,7 +28,7 @@ public class OutboundMessageFragments {
           private UDPTransport _transport;
           // private ActiveThrottle _throttle; // LINT not used ??
           /** peers we are actively sending messages to */
      -    private final List _activePeers;
      +    private final List _activePeers;
           private boolean _alive;
           /** which peer should we build the next packet out of? */
           private int _nextPeer;
      @@ -207,7 +207,7 @@ public class OutboundMessageFragments {
               synchronized (_activePeers) {
                   peers = new ArrayList(_activePeers.size());
                   for (int i = 0; i < _activePeers.size(); i++) {
      -                PeerState state = (PeerState)_activePeers.get(i);
      +                PeerState state = _activePeers.get(i);
                       if (state.getOutboundMessageCount() <= 0) {
                           _activePeers.remove(i);
                           i--;
      @@ -255,7 +255,7 @@ public class OutboundMessageFragments {
                                   if (cycleTime > 1000)
                                       _context.statManager().addRateData("udp.sendCycleTimeSlow", cycleTime, _activePeers.size());
                               }
      -                        peer = (PeerState)_activePeers.get(i);
      +                        peer = _activePeers.get(i);
                               state = peer.allocateSend();
                               if (state != null) {
                                   _nextPeer = i + 1;
      @@ -318,12 +318,12 @@ public class OutboundMessageFragments {
                       return null;
       
                   // ok, simplest possible thing is to always tack on the bitfields if
      -            List msgIds = peer.getCurrentFullACKs();
      +            List msgIds = peer.getCurrentFullACKs();
                   if (msgIds == null) msgIds = new ArrayList();
      -            List partialACKBitfields = new ArrayList();
      +            List partialACKBitfields = new ArrayList();
                   peer.fetchPartialACKs(partialACKBitfields);
                   int piggybackedPartialACK = partialACKBitfields.size();
      -            List remaining = new ArrayList(msgIds);
      +            List remaining = new ArrayList(msgIds);
                   int sparseCount = 0;
                   UDPPacket rv[] = new UDPPacket[fragments]; //sparse
                   for (int i = 0; i < fragments; i++) {
      @@ -356,7 +356,7 @@ public class OutboundMessageFragments {
                   int piggybackedAck = 0;
                   if (msgIds.size() != remaining.size()) {
                       for (int i = 0; i < msgIds.size(); i++) {
      -                    Long id = (Long)msgIds.get(i);
      +                    Long id = msgIds.get(i);
                           if (!remaining.contains(id)) {
                               peer.removeACKMessage(id);
                               piggybackedAck++;
      diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java
      index 1a9a9c590..877ad10b1 100644
      --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java
      +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageState.java
      @@ -342,8 +342,8 @@ public class OutboundMessageState {
           public String toString() {
               short sends[] = _fragmentSends;
               ByteArray messageBuf = _messageBuf;
      -        StringBuilder buf = new StringBuilder(64);
      -        buf.append("Message ").append(_messageId);
      +        StringBuilder buf = new StringBuilder(256);
      +        buf.append("OB Message ").append(_messageId);
               if (sends != null)
                   buf.append(" with ").append(sends.length).append(" fragments");
               if (messageBuf != null)
      diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java b/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java
      index b2136ac8d..841588ee4 100644
      --- a/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java
      +++ b/router/java/src/net/i2p/router/transport/udp/OutboundRefiller.java
      @@ -31,8 +31,7 @@ public class OutboundRefiller implements Runnable {
           
           public void startup() {
               _alive = true;
      -        I2PThread t = new I2PThread(this, "UDP outbound refiller");
      -        t.setDaemon(true);
      +        I2PThread t = new I2PThread(this, "UDP outbound refiller", true);
               t.start();
           }
           public void shutdown() { _alive = false; }
      diff --git a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
      index 75258791e..26b82e667 100644
      --- a/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
      +++ b/router/java/src/net/i2p/router/transport/udp/PacketBuilder.java
      @@ -2,8 +2,8 @@ package net.i2p.router.transport.udp;
       
       import java.net.InetAddress;
       import java.net.UnknownHostException;
      -import java.util.ArrayList;
       import java.util.Arrays;
      +import java.util.Collections;
       import java.util.Date;
       import java.util.List;
       
      @@ -32,6 +32,25 @@ public class PacketBuilder {
           private static final ByteCache _hmacCache = ByteCache.getInstance(64, Hash.HASH_LENGTH);
           private static final ByteCache _blockCache = ByteCache.getInstance(64, 16);
       
      +    /**
      +     *  For debugging and stats only - does not go out on the wire.
      +     *  These are chosen to be higher than the highest I2NP message type,
      +     *  as a data packet is set to the underlying I2NP message type.
      +     */
      +    static final int TYPE_FIRST = 42;
      +    static final int TYPE_ACK = TYPE_FIRST;
      +    static final int TYPE_PUNCH = 43;
      +    static final int TYPE_RESP = 44;
      +    static final int TYPE_INTRO = 45;
      +    static final int TYPE_RREQ = 46;
      +    static final int TYPE_TCB = 47;
      +    static final int TYPE_TBC = 48;
      +    static final int TYPE_TTA = 49;
      +    static final int TYPE_TFA = 50;
      +    static final int TYPE_CONF = 51;
      +    static final int TYPE_SREQ = 52;
      +    static final int TYPE_CREAT = 53;
      +
           /** we only talk to people of the right version */
           static final int PROTOCOL_VERSION = 0;
           
      @@ -58,7 +77,7 @@ public class PacketBuilder {
            *                        The list itself is passed by reference, and if a messageId is
            *                        included, it should be removed from the list.
            */
      -    public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer, List ackIdsRemaining, List partialACKsRemaining) {
      +    public UDPPacket buildPacket(OutboundMessageState state, int fragment, PeerState peer, List ackIdsRemaining, List partialACKsRemaining) {
               UDPPacket packet = UDPPacket.acquire(_context, false);
       
               StringBuilder msg = null;
      @@ -92,18 +111,18 @@ public class PacketBuilder {
               // is under the MTU, but for now, since the # of packets acked is so few (usually
               // just one or two), and since the packets are so small anyway, an additional five
               // or ten bytes doesn't hurt.
      -        if ( (ackIdsRemaining != null) && (ackIdsRemaining.size() > 0) )
      +        if ( (ackIdsRemaining != null) && (!ackIdsRemaining.isEmpty()) )
                   data[off] |= UDPPacket.DATA_FLAG_EXPLICIT_ACK;
      -        if ( (partialACKsRemaining != null) && (partialACKsRemaining.size() > 0) )
      +        if ( (partialACKsRemaining != null) && (!partialACKsRemaining.isEmpty()) )
                   data[off] |= UDPPacket.DATA_FLAG_ACK_BITFIELDS;
               off++;
       
      -        if ( (ackIdsRemaining != null) && (ackIdsRemaining.size() > 0) ) {
      +        if ( (ackIdsRemaining != null) && (!ackIdsRemaining.isEmpty()) ) {
                   DataHelper.toLong(data, off, 1, ackIdsRemaining.size());
                   off++;
                   for (int i = 0; i < ackIdsRemaining.size(); i++) {
                   //while (ackIdsRemaining.size() > 0) {
      -                Long ackId = (Long)ackIdsRemaining.get(i);//(Long)ackIdsRemaining.remove(0);
      +                Long ackId = ackIdsRemaining.get(i);//(Long)ackIdsRemaining.remove(0);
                       DataHelper.toLong(data, off, 4, ackId.longValue());
                       off += 4;        
                       if (msg != null) // logging it
      @@ -118,7 +137,7 @@ public class PacketBuilder {
                   // leave it blank for now, since we could skip some
                   off++;
                   for (int i = 0; i < partialACKsRemaining.size(); i++) {
      -                ACKBitfield bitfield = (ACKBitfield)partialACKsRemaining.get(i);
      +                ACKBitfield bitfield = partialACKsRemaining.get(i);
                       if (bitfield.receivedComplete()) continue;
                       DataHelper.toLong(data, off, 4, bitfield.getMessageId());
                       off += 4;
      @@ -214,15 +233,18 @@ public class PacketBuilder {
           // We use this for keepalive purposes.
           // It doesn't generate a reply, but that's ok.
           public UDPPacket buildPing(PeerState peer) {
      -        return buildACK(peer, new ArrayList(0));
      +        return buildACK(peer, Collections.EMPTY_LIST);
           }
       
           private static final int ACK_PRIORITY = 1;
           
           /**
      +     *  Build the ack packet. The list need not be sorted into full and partial;
      +     *  this method will put all fulls before the partials in the outgoing packet.
      +     *
            * @param ackBitfields list of ACKBitfield instances to either fully or partially ACK
            */
      -    public UDPPacket buildACK(PeerState peer, List ackBitfields) {
      +    public UDPPacket buildACK(PeerState peer, List ackBitfields) {
               UDPPacket packet = UDPPacket.acquire(_context, false);
               
               StringBuilder msg = null;
      @@ -263,7 +285,7 @@ public class PacketBuilder {
                   DataHelper.toLong(data, off, 1, fullACKCount);
                   off++;
                   for (int i = 0; i < ackBitfields.size(); i++) {
      -                ACKBitfield bf = (ACKBitfield)ackBitfields.get(i);
      +                ACKBitfield bf = ackBitfields.get(i);
                       if (bf.receivedComplete()) {
                           DataHelper.toLong(data, off, 4, bf.getMessageId());
                           off += 4;
      @@ -415,7 +437,7 @@ public class PacketBuilder {
               authenticate(packet, ourIntroKey, ourIntroKey, iv);
               setTo(packet, to, state.getSentPort());
               _ivCache.release(iv);
      -        packet.setMessageType(53);
      +        packet.setMessageType(TYPE_CREAT);
               return packet;
           }
           
      @@ -479,7 +501,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(off);
               authenticate(packet, state.getIntroKey(), state.getIntroKey());
               setTo(packet, to, state.getSentPort());
      -        packet.setMessageType(52);
      +        packet.setMessageType(TYPE_SREQ);
               return packet;
           }
       
      @@ -586,7 +608,7 @@ public class PacketBuilder {
               } 
               
               setTo(packet, to, state.getSentPort());
      -        packet.setMessageType(51);
      +        packet.setMessageType(TYPE_CONF);
               return packet;
           }
       
      @@ -639,7 +661,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(off);
               authenticate(packet, toCipherKey, toMACKey);
               setTo(packet, toIP, toPort);
      -        packet.setMessageType(50);
      +        packet.setMessageType(TYPE_TFA);
               return packet;
           }
       
      @@ -684,7 +706,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(off);
               authenticate(packet, aliceIntroKey, aliceIntroKey);
               setTo(packet, aliceIP, alicePort);
      -        packet.setMessageType(49);
      +        packet.setMessageType(TYPE_TTA);
               return packet;
           }
       
      @@ -731,7 +753,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(off);
               authenticate(packet, charlieCipherKey, charlieMACKey);
               setTo(packet, charlieIP, charliePort);
      -        packet.setMessageType(48);
      +        packet.setMessageType(TYPE_TBC);
               return packet;
           }
           
      @@ -776,7 +798,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(off);
               authenticate(packet, bobCipherKey, bobMACKey);
               setTo(packet, bobIP, bobPort);
      -        packet.setMessageType(47);
      +        packet.setMessageType(TYPE_TCB);
               return packet;
           }
           
      @@ -875,7 +897,7 @@ public class PacketBuilder {
               if (encrypt)
                   authenticate(packet, new SessionKey(introKey), new SessionKey(introKey));
               setTo(packet, introHost, introPort);
      -        packet.setMessageType(46);
      +        packet.setMessageType(TYPE_RREQ);
               return packet;
           }
       
      @@ -925,7 +947,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(off);
               authenticate(packet, charlie.getCurrentCipherKey(), charlie.getCurrentMACKey());
               setTo(packet, charlie.getRemoteIPAddress(), charlie.getRemotePort());
      -        packet.setMessageType(45);
      +        packet.setMessageType(TYPE_INTRO);
               return packet;
           }
       
      @@ -986,7 +1008,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(off);
               authenticate(packet, aliceIntroKey, aliceIntroKey);
               setTo(packet, aliceAddr, alice.getPort());
      -        packet.setMessageType(44);
      +        packet.setMessageType(TYPE_RESP);
               return packet;
           }
           
      @@ -1019,7 +1041,7 @@ public class PacketBuilder {
               packet.getPacket().setLength(0);
               setTo(packet, to, port);
               
      -        packet.setMessageType(43);
      +        packet.setMessageType(TYPE_PUNCH);
               return packet;
           }
           
      diff --git a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
      index e32530809..1a35c5d19 100644
      --- a/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
      +++ b/router/java/src/net/i2p/router/transport/udp/PacketHandler.java
      @@ -1,8 +1,6 @@
       package net.i2p.router.transport.udp;
       
      -import java.util.ArrayList;
       import java.util.Date;
      -import java.util.List;
       
       import net.i2p.router.Router;
       import net.i2p.router.RouterContext;
      @@ -31,7 +29,7 @@ public class PacketHandler {
           private PeerTestManager _testManager;
           private IntroductionManager _introManager;
           private boolean _keepReading;
      -    private List _handlers;
      +    private final Handler[] _handlers;
           
           private static final int NUM_HANDLERS = 5;
           /** let packets be up to 30s slow */
      @@ -46,9 +44,9 @@ public class PacketHandler {
               _inbound = inbound;
               _testManager = testManager;
               _introManager = introManager;
      -        _handlers = new ArrayList(NUM_HANDLERS);
      +        _handlers = new Handler[NUM_HANDLERS];
               for (int i = 0; i < NUM_HANDLERS; i++) {
      -            _handlers.add(new Handler());
      +            _handlers[i] = new Handler();
               }
               _context.statManager().createRateStat("udp.handleTime", "How long it takes to handle a received packet after its been pulled off the queue", "udp", UDPTransport.RATES);
               _context.statManager().createRateStat("udp.queueTime", "How long after a packet is received can we begin handling it", "udp", UDPTransport.RATES);
      @@ -81,9 +79,8 @@ public class PacketHandler {
           
           public void startup() { 
               _keepReading = true;
      -        for (int i = 0; i < _handlers.size(); i++) {
      -            I2PThread t = new I2PThread((Handler)_handlers.get(i), "UDP Packet handler " + i + "/" + _handlers.size());
      -            t.setDaemon(true);
      +        for (int i = 0; i < NUM_HANDLERS; i++) {
      +            I2PThread t = new I2PThread(_handlers[i], "UDP Packet handler " + i + '/' + NUM_HANDLERS, true);
                   t.start();
               }
           }
      @@ -94,10 +91,9 @@ public class PacketHandler {
       
           String getHandlerStatus() {
               StringBuilder rv = new StringBuilder();
      -        int size = _handlers.size();
      -        rv.append("Handlers: ").append(size);
      -        for (int i = 0; i < size; i++) {
      -            Handler handler = (Handler)_handlers.get(i);
      +        rv.append("Handlers: ").append(NUM_HANDLERS);
      +        for (int i = 0; i < NUM_HANDLERS; i++) {
      +            Handler handler = _handlers[i];
                   rv.append(" handler ").append(i).append(" state: ").append(handler._state);
               }
               return rv.toString();
      diff --git a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java
      index 582eed1c3..271e83597 100644
      --- a/router/java/src/net/i2p/router/transport/udp/PacketPusher.java
      +++ b/router/java/src/net/i2p/router/transport/udp/PacketPusher.java
      @@ -25,8 +25,7 @@ public class PacketPusher implements Runnable {
           
           public void startup() {
               _alive = true;
      -        I2PThread t = new I2PThread(this, "UDP packet pusher");
      -        t.setDaemon(true);
      +        I2PThread t = new I2PThread(this, "UDP packet pusher", true);
               t.start();
           }
           
      @@ -39,7 +38,8 @@ public class PacketPusher implements Runnable {
                       if (packets != null) {
                           for (int i = 0; i < packets.length; i++) {
                               if (packets[i] != null) // null for ACKed fragments
      -                            _sender.add(packets[i], 0); // 0 does not block //100); // blocks for up to 100ms
      +                            //_sender.add(packets[i], 0); // 0 does not block //100); // blocks for up to 100ms
      +                            _sender.add(packets[i]);
                           }
                       }
                   } catch (Exception e) {
      diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java
      index f36e2d95c..06e528a94 100644
      --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java
      +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java
      @@ -8,12 +8,16 @@ import java.util.HashMap;
       import java.util.Iterator;
       import java.util.List;
       import java.util.Map;
      +import java.util.Set;
      +import java.util.Queue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.data.Hash;
       import net.i2p.data.SessionKey;
       import net.i2p.router.OutNetMessage;
       import net.i2p.router.RouterContext;
       import net.i2p.util.Log;
      +import net.i2p.util.ConcurrentHashSet;
       
       /**
        * Contain all of the state about a UDP connection to a peer.
      @@ -73,14 +77,22 @@ public class PeerState {
           private int _consecutiveFailedSends;
           /** when did we last have a failed send (beginning of period) */
           // private long _lastFailedSendPeriod;
      -    /** list of messageIds (Long) that we have received but not yet sent */
      -    private final List _currentACKs;
      +
      +    /**
      +     *  Set of messageIds (Long) that we have received but not yet sent
      +     *  Since even with the smallest MTU we can fit 131 acks in a message,
      +     *  we are unlikely to get backed up on acks, so we don't keep
      +     *  them in any particular order.
      +     */
      +    private final Set _currentACKs;
      +
           /** 
            * list of the most recent messageIds (Long) that we have received and sent
            * an ACK for.  We keep a few of these around to retransmit with _currentACKs,
            * hopefully saving some spurious retransmissions
            */
      -    private final List _currentACKsResend;
      +    private final Queue _currentACKsResend;
      +
           /** when did we last send ACKs to the peer? */
           private volatile long _lastACKSend;
           /** when did we decide we need to ACK to this peer? */
      @@ -169,9 +181,9 @@ public class PeerState {
           private long _packetsReceived;
           
           /** list of InboundMessageState for active message */
      -    private final Map _inboundMessages;
      +    private final Map _inboundMessages;
           /** list of OutboundMessageState */
      -    private final List _outboundMessages;
      +    private final List _outboundMessages;
           /** which outbound message is currently being retransmitted */
           private OutboundMessageState _retransmitter;
           
      @@ -180,8 +192,10 @@ public class PeerState {
           /** have we migrated away from this peer to another newer one? */
           private volatile boolean _dead;
       
      +    /** Make sure a 4229 byte TunnelBuildMessage can be sent in one volley with small MTU */
      +    private static final int MIN_CONCURRENT_MSGS = 8;
           /** how many concurrent outbound messages do we allow throws OutboundMessageFragments to send */
      -    private volatile int _concurrentMessagesAllowed = 8;
      +    private volatile int _concurrentMessagesAllowed = MIN_CONCURRENT_MSGS;
           /** 
            * how many outbound messages are currently being transmitted.  Not thread safe, as we're not strict
            */
      @@ -203,6 +217,11 @@ public class PeerState {
            * we need 522 fragment bytes to fit it in 2 packets - add 46 for SSU, 20 
            * for UDP, and 8 for IP, giving us 596.  round up to mod 16, giving a total
            * of 608
      +     *
      +     * Well, we really need to count the acks as well, especially
      +     * 4 * MAX_RESEND_ACKS which can take up a significant amount of space.
      +     * We reduce the max acks when using the small MTU but it may not be enough...
      +     *
            */
           private static final int MIN_MTU = 608;//600; //1500;
           private static final int DEFAULT_MTU = MIN_MTU;
      @@ -234,8 +253,8 @@ public class PeerState {
               _currentReceiveSecond = -1;
               _lastSendTime = -1;
               _lastReceiveTime = -1;
      -        _currentACKs = new ArrayList(8);
      -        _currentACKsResend = new ArrayList(8);
      +        _currentACKs = new ConcurrentHashSet();
      +        _currentACKsResend = new LinkedBlockingQueue();
               _currentSecondECNReceived = false;
               _remoteWantsPreviousACKs = false;
               _sendWindowBytes = DEFAULT_SEND_WINDOW_BYTES;
      @@ -582,12 +601,9 @@ public class PeerState {
                  _context.statManager().addRateData("udp.receiveBps", _receiveBps, 0);
               }
               
      -        synchronized (_currentACKs) {
      -            if (_wantACKSendSince <= 0)
      -                _wantACKSendSince = now;
      -            if (!_currentACKs.contains(messageId))
      -                _currentACKs.add(messageId);
      -        }
      +        if (_wantACKSendSince <= 0)
      +            _wantACKSendSince = now;
      +        _currentACKs.add(messageId);
               _messagesReceived++;
           }
           
      @@ -600,7 +616,8 @@ public class PeerState {
            * Fetch the internal id (Long) to InboundMessageState for incomplete inbound messages.
            * Access to this map must be synchronized explicitly!
            */
      -    public Map getInboundMessages() { return _inboundMessages; }
      +    public Map getInboundMessages() { return _inboundMessages; }
      +
           /**
            * Expire partially received inbound messages, returning how many are still pending.
            * This should probably be fired periodically, in case a peer goes silent and we don't
      @@ -661,26 +678,36 @@ public class PeerState {
            * removeACKMessage(Long) should be called.
            *
            */
      -    public List getCurrentFullACKs() {
      -        synchronized (_currentACKs) {
      -            ArrayList rv = new ArrayList(_currentACKs);
      +    public List getCurrentFullACKs() {
      +            ArrayList rv = new ArrayList(_currentACKs);
                   // include some for retransmission
                   rv.addAll(_currentACKsResend);
                   return rv;
      -        }
           }
      +
           public void removeACKMessage(Long messageId) {
      -        synchronized (_currentACKs) {
                   _currentACKs.remove(messageId);
      -            _currentACKsResend.add(messageId);
      +            _currentACKsResend.offer(messageId);
                   // trim down the resends
                   while (_currentACKsResend.size() > MAX_RESEND_ACKS)
      -                _currentACKsResend.remove(0);
      -        }
      -        _lastACKSend = _context.clock().now();
      +                _currentACKsResend.poll();
      +            _lastACKSend = _context.clock().now();
           }
           
      +    /**
      +     *  The max number of acks we save to send as duplicates
      +     */
           private static final int MAX_RESEND_ACKS = 16;
      +    /**
      +     *  The number of duplicate acks sent in each messge -
      +     *  Warning, this directly affects network overhead
      +     *  Was 16 but that's too much (64 bytes in a max 608 byte packet,
      +     *  and often much smaller)
      +     *  @since 0.7.13
      +     */
      +    private static final int MAX_RESEND_ACKS_LARGE = 9;
      +    /** for small MTU */
      +    private static final int MAX_RESEND_ACKS_SMALL = 4;
           
           /** 
            * grab a list of ACKBitfield instances, some of which may fully 
      @@ -691,51 +718,75 @@ public class PeerState {
            * will be unchanged if there are ACKs remaining.
            *
            */
      -    public List retrieveACKBitfields() { return retrieveACKBitfields(true); }
      -    public List retrieveACKBitfields(boolean alwaysIncludeRetransmissions) {
      -        List rv = null;
      +    public List retrieveACKBitfields() { return retrieveACKBitfields(true); }
      +
      +    public List retrieveACKBitfields(boolean alwaysIncludeRetransmissions) {
      +        List rv = new ArrayList(MAX_RESEND_ACKS);
               int bytesRemaining = countMaxACKData();
      -        synchronized (_currentACKs) {
      -            rv = new ArrayList(16); //_currentACKs.size());
      -            int oldIndex = _currentACKsResend.size();
      -            while ( (bytesRemaining >= 4) && (_currentACKs.size() > 0) ) {
      -                Long val = (Long)_currentACKs.remove(0);
      +
      +            // Limit the overhead of all the resent acks when using small MTU
      +            // 64 bytes in a 608-byte packet is too much...
      +            // Send a random subset of all the queued resend acks.
      +            int resendSize = _currentACKsResend.size();
      +            int maxResendAcks;
      +            if (bytesRemaining < MIN_MTU)
      +                maxResendAcks = MAX_RESEND_ACKS_SMALL;
      +            else
      +                maxResendAcks = MAX_RESEND_ACKS_LARGE;
      +            List randomResends = new ArrayList(_currentACKsResend);
      +
      +            // As explained above, we include the acks in any order
      +            // since we are unlikely to get backed up -
      +            // just take them using the Set iterator.
      +            Iterator iter = _currentACKs.iterator();
      +            while (bytesRemaining >= 4 && iter.hasNext()) {
      +                Long val = iter.next();
      +                iter.remove();
                       long id = val.longValue();
                       rv.add(new FullACKBitfield(id));
      -                _currentACKsResend.add(val);
      +                _currentACKsResend.offer(val);
                       bytesRemaining -= 4;
                   }
      -            if (_currentACKs.size() <= 0)
      +            if (_currentACKs.isEmpty())
                       _wantACKSendSince = -1;
                   if (alwaysIncludeRetransmissions || rv.size() > 0) {
                       // now repeat by putting in some old ACKs
      -                for (int i = 0; (i < oldIndex) && (bytesRemaining >= 4); i++) {
      -                    Long cur = (Long)_currentACKsResend.get(i);
      +                // randomly selected from the Resend queue.
      +                // Maybe we should only resend each one a certain number of times...
      +                int oldIndex = Math.min(resendSize, maxResendAcks);
      +                if (oldIndex > 0 && oldIndex < resendSize)
      +                    Collections.shuffle(randomResends, _context.random());
      +                iter = randomResends.iterator();
      +                while (bytesRemaining >= 4 && oldIndex-- > 0 && iter.hasNext()) {
      +                    Long cur = iter.next();
                           long c = cur.longValue();
                           FullACKBitfield bf = new FullACKBitfield(c);
      -                    rv.add(bf);
      -                    bytesRemaining -= 4;
      +                    // try to avoid duplicates ??
      +                    // ACKsResend is not checked for dups at add time
      +                    //if (rv.contains(bf)) {
      +                    //    iter.remove();
      +                    //} else {
      +                        rv.add(bf);
      +                        bytesRemaining -= 4;
      +                    //}
                       }
                   }
                   // trim down the resends
                   while (_currentACKsResend.size() > MAX_RESEND_ACKS)
      -                _currentACKsResend.remove(0);
      -        }
      +                _currentACKsResend.poll();
       
               int partialIncluded = 0;
               if (bytesRemaining > 4) {
                   // ok, there's room to *try* to fit in some partial ACKs, so
                   // we should try to find some packets to partially ACK 
                   // (preferably the ones which have the most received fragments)
      -            List partial = new ArrayList();
      +            List partial = new ArrayList();
                   fetchPartialACKs(partial);
                   // we may not be able to use them all, but lets try...
                   for (int i = 0; (bytesRemaining > 4) && (i < partial.size()); i++) {
      -                ACKBitfield bitfield = (ACKBitfield)partial.get(i);
      +                ACKBitfield bitfield = partial.get(i);
                       int bytes = (bitfield.fragmentCount() / 7) + 1;
                       if (bytesRemaining > bytes + 4) { // msgId + bitfields
      -                    if (rv == null)
      -                        rv = new ArrayList(partial.size());
                           rv.add(bitfield);
                           bytesRemaining -= bytes + 4;
                           partialIncluded++;
      @@ -754,7 +805,7 @@ public class PeerState {
               return rv;
           }
           
      -    void fetchPartialACKs(List rv) {
      +    void fetchPartialACKs(List rv) {
               InboundMessageState states[] = null;
               int curState = 0;
               synchronized (_inboundMessages) {
      @@ -762,9 +813,8 @@ public class PeerState {
                   if (numMessages <= 0) 
                       return;
                   // todo: make this a list instead of a map, so we can iterate faster w/out the memory overhead?
      -            int remaining = _inboundMessages.size();
      -            for (Iterator iter = _inboundMessages.values().iterator(); remaining > 0; remaining--) {
      -                InboundMessageState state = (InboundMessageState)iter.next();
      +            for (Iterator iter = _inboundMessages.values().iterator(); iter.hasNext(); ) {
      +                InboundMessageState state = iter.next();
                       if (state.isExpired()) {
                           //if (_context instanceof RouterContext)
                           //    ((RouterContext)_context).messageHistory().droppedInboundMessage(state.getMessageId(), state.getFrom(), "expired partially received: " + state.toString());
      @@ -795,6 +845,13 @@ public class PeerState {
               public boolean received(int fragmentNum) { return true; }
               public boolean receivedComplete() { return true; }
               @Override
      +        public int hashCode() { return (int) _msgId; }
      +        @Override
      +        public boolean equals(Object o) {
      +            if (!(o instanceof FullACKBitfield)) return false;
      +            return _msgId == ((ACKBitfield)o).getMessageId();
      +        }
      +        @Override
               public String toString() { return "Full ACK of " + _msgId; }
           }
               
      @@ -825,8 +882,8 @@ public class PeerState {
                   }
               } else {
                   int allow = _concurrentMessagesAllowed - 1;
      -            if (allow < 8)
      -                allow = 8;
      +            if (allow < MIN_CONCURRENT_MSGS)
      +                allow = MIN_CONCURRENT_MSGS;
                   _concurrentMessagesAllowed = allow;
               }
               if (_sendWindowBytes > MAX_SEND_WINDOW_BYTES)
      @@ -977,10 +1034,10 @@ public class PeerState {
           public long getWantedACKSendSince() { return _wantACKSendSince; }
           public boolean unsentACKThresholdReached() {
               int threshold = countMaxACKData() / 4;
      -        synchronized (_currentACKs) {
      -            return _currentACKs.size() >= threshold;
      -        }
      +        return _currentACKs.size() >= threshold;
           }
      +
      +    /** @return MTU - 83 */
           private int countMaxACKData() {
               return _mtu 
                       - IP_HEADER_SIZE
      @@ -1013,7 +1070,7 @@ public class PeerState {
               state.setPeer(this);
               if (_log.shouldLog(Log.DEBUG))
                   _log.debug("Adding to " + _remotePeer.toBase64() + ": " + state.getMessageId());
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               if (msgs == null) return 0;
               int rv = 0;
               boolean fail = false;
      @@ -1068,12 +1125,12 @@ public class PeerState {
           public void dropOutbound() {
               //if (_dead) return;
               _dead = true;
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               //_outboundMessages = null;
               _retransmitter = null;
               if (msgs != null) {
                   int sz = 0;
      -            List tempList = null;
      +            List tempList = null;
                   synchronized (msgs) {
                       sz = msgs.size();
                       if (sz > 0) {
      @@ -1082,12 +1139,14 @@ public class PeerState {
       		}
                   }
                   for (int i = 0; i < sz; i++)
      -                _transport.failed((OutboundMessageState)tempList.get(i), false);
      +                _transport.failed(tempList.get(i), false);
               }
      +        // so the ACKSender will drop this peer from its queue
      +        _wantACKSendSince = -1;
           }
           
           public int getOutboundMessageCount() {
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               if (_dead) return 0;
               if (msgs != null) {
                   synchronized (msgs) {
      @@ -1104,17 +1163,17 @@ public class PeerState {
            */
           public int finishMessages() {
               int rv = 0;
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               if (_dead) {
                   dropOutbound();
                   return 0;
       	}
      -        List succeeded = null;
      -        List failed = null;
      +        List succeeded = null;
      +        List failed = null;
               synchronized (msgs) {
                   int size = msgs.size();
                   for (int i = 0; i < size; i++) {
      -                OutboundMessageState state = (OutboundMessageState)msgs.get(i);
      +                OutboundMessageState state = msgs.get(i);
                       if (state.isComplete()) {
                           msgs.remove(i);
                           i--;
      @@ -1147,7 +1206,7 @@ public class PeerState {
               }
               
               for (int i = 0; succeeded != null && i < succeeded.size(); i++) {
      -            OutboundMessageState state = (OutboundMessageState)succeeded.get(i);
      +            OutboundMessageState state = succeeded.get(i);
                   _transport.succeeded(state);
                   state.releaseResources();
                   OutNetMessage msg = state.getMessage();
      @@ -1156,7 +1215,7 @@ public class PeerState {
               }
               
               for (int i = 0; failed != null && i < failed.size(); i++) {
      -            OutboundMessageState state = (OutboundMessageState)failed.get(i);
      +            OutboundMessageState state = failed.get(i);
                   OutNetMessage msg = state.getMessage();
                   if (msg != null) {
                       msg.timestamp("expired in the active pool");
      @@ -1180,12 +1239,12 @@ public class PeerState {
            */
           public OutboundMessageState allocateSend() {
               int total = 0;
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               if (_dead) return null;
               synchronized (msgs) {
                   int size = msgs.size();
                   for (int i = 0; i < size; i++) {
      -                OutboundMessageState state = (OutboundMessageState)msgs.get(i);
      +                OutboundMessageState state = msgs.get(i);
                       if (locked_shouldSend(state)) {
                           if (_log.shouldLog(Log.DEBUG))
                               _log.debug("Allocate sending to " + _remotePeer.toBase64() + ": " + state.getMessageId());
      @@ -1217,7 +1276,7 @@ public class PeerState {
           public int getNextDelay() {
               int rv = -1;
               long now = _context.clock().now();
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               if (_dead) return -1;
               synchronized (msgs) {
                   if (_retransmitter != null) {
      @@ -1229,7 +1288,7 @@ public class PeerState {
                   }
                   int size = msgs.size();
                   for (int i = 0; i < size; i++) {
      -                OutboundMessageState state = (OutboundMessageState)msgs.get(i);
      +                OutboundMessageState state = msgs.get(i);
                       int delay = (int)(state.getNextSendTime() - now);
                       if (delay <= 0)
                           delay = 1;
      @@ -1346,12 +1405,12 @@ public class PeerState {
           
           public int acked(long messageId) {
               OutboundMessageState state = null;
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               if (_dead) return 0;
               synchronized (msgs) {
                   int sz = msgs.size();
                   for (int i = 0; i < sz; i++) {
      -                state = (OutboundMessageState)msgs.get(i);
      +                state = msgs.get(i);
                       if (state.getMessageId() == messageId) {
                           msgs.remove(i);
                           break;
      @@ -1407,13 +1466,13 @@ public class PeerState {
                   return;
               }
           
      -        List msgs = _outboundMessages;
      +        List msgs = _outboundMessages;
               
               OutboundMessageState state = null;
               boolean isComplete = false;
               synchronized (msgs) {
                   for (int i = 0; i < msgs.size(); i++) {
      -                state = (OutboundMessageState)msgs.get(i);
      +                state = msgs.get(i);
                       if (state.getMessageId() == bitfield.getMessageId()) {
                           boolean complete = state.acked(bitfield);
                           if (complete) {
      @@ -1486,26 +1545,23 @@ public class PeerState {
               _sendWindowBytes = oldPeer._sendWindowBytes;
               oldPeer._dead = true;
               
      -        List tmp = new ArrayList();
      -        synchronized (oldPeer._currentACKs) {
      -            tmp.addAll(oldPeer._currentACKs);
      -            oldPeer._currentACKs.clear();
      -        }
      +        List tmp = new ArrayList();
      +        tmp.addAll(oldPeer._currentACKs);
      +        oldPeer._currentACKs.clear();
      +
               if (!_dead) {
      -            synchronized (_currentACKs) { _currentACKs.addAll(tmp); }
      +            _currentACKs.addAll(tmp);
       	}
               tmp.clear();
               
      -        synchronized (oldPeer._currentACKsResend) {
      -            tmp.addAll(oldPeer._currentACKsResend);
      -            oldPeer._currentACKsResend.clear();
      -        }
      +        tmp.addAll(oldPeer._currentACKsResend);
      +        oldPeer._currentACKsResend.clear();
      +
               if (!_dead) {
      -            synchronized (_currentACKsResend) { _currentACKsResend.addAll(tmp); }
      +            _currentACKsResend.addAll(tmp);
       	}
      -        tmp.clear();
               
      -        Map msgs = new HashMap();
      +        Map msgs = new HashMap();
               synchronized (oldPeer._inboundMessages) {
                   msgs.putAll(oldPeer._inboundMessages);
                   oldPeer._inboundMessages.clear();
      @@ -1515,20 +1571,20 @@ public class PeerState {
       	}
               msgs.clear();
               
      +        List tmp2 = new ArrayList();
               OutboundMessageState retransmitter = null;
               synchronized (oldPeer._outboundMessages) {
      -            tmp.addAll(oldPeer._outboundMessages);
      +            tmp2.addAll(oldPeer._outboundMessages);
                   oldPeer._outboundMessages.clear();
                   retransmitter = oldPeer._retransmitter;
                   oldPeer._retransmitter = null;
               }
               if (!_dead) {
                   synchronized (_outboundMessages) {
      -                _outboundMessages.addAll(tmp);
      +                _outboundMessages.addAll(tmp2);
                       _retransmitter = retransmitter;
                   }
               }
      -        tmp.clear();
           }
       
           /*
      diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
      index 9be1c26ad..aa145bbe9 100644
      --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
      +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
      @@ -2,11 +2,10 @@ package net.i2p.router.transport.udp;
       
       import java.net.InetAddress;
       import java.net.UnknownHostException;
      -import java.util.ArrayList;
      -import java.util.Collections;
      -import java.util.HashMap;
      -import java.util.List;
       import java.util.Map;
      +import java.util.Queue;
      +import java.util.concurrent.ConcurrentHashMap;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.data.Base64;
       import net.i2p.data.DataHelper;
      @@ -102,7 +101,7 @@ class PeerTestManager {
           private PeerTestState _currentTest;
           private boolean _currentTestComplete;
           /** as Alice */
      -    private List _recentTests;
      +    private Queue _recentTests;
           
           /** longest we will keep track of a Charlie nonce for */
           private static final int MAX_CHARLIE_LIFETIME = 10*1000;
      @@ -116,8 +115,8 @@ class PeerTestManager {
               _context = context;
               _transport = transport;
               _log = context.logManager().getLog(PeerTestManager.class);
      -        _activeTests = new HashMap(64);
      -        _recentTests = Collections.synchronizedList(new ArrayList(16));
      +        _activeTests = new ConcurrentHashMap();
      +        _recentTests = new LinkedBlockingQueue();
               _packetBuilder = new PacketBuilder(context, transport);
               _currentTest = null;
               _currentTestComplete = false;
      @@ -155,8 +154,8 @@ class PeerTestManager {
               if (_log.shouldLog(Log.DEBUG))
                   _log.debug("Running test with bob = " + bobIP + ":" + bobPort + " " + test.getNonce());
               while (_recentTests.size() > 16)
      -            _recentTests.remove(0);
      -        _recentTests.add(new Long(test.getNonce()));
      +            _recentTests.poll();
      +        _recentTests.offer(new Long(test.getNonce()));
               
               sendTestToBob();
               
      @@ -435,10 +434,7 @@ class PeerTestManager {
                   testInfo.readIP(testIP, 0);
               }
              
      -        PeerTestState state = null;
      -        synchronized (_activeTests) {
      -            state = (PeerTestState)_activeTests.get(new Long(nonce));
      -        }
      +        PeerTestState state = _activeTests.get(new Long(nonce));
               
               if (state == null) {
                   if ( (testIP == null) || (testPort <= 0) ) {
      @@ -542,9 +538,7 @@ class PeerTestManager {
                       _log.debug("Receive from bob (" + from + ") as charlie, sending back to bob and sending to alice @ " + aliceIP + ":" + alicePort);
                   
                   if (isNew) {
      -                synchronized (_activeTests) {
      -                    _activeTests.put(new Long(nonce), state);
      -                }
      +                _activeTests.put(new Long(nonce), state);
                       SimpleScheduler.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
                   }
       
      @@ -623,9 +617,7 @@ class PeerTestManager {
                   }
                   
                   if (isNew) {
      -                synchronized (_activeTests) {
      -                    _activeTests.put(new Long(nonce), state);
      -                }
      +                _activeTests.put(new Long(nonce), state);
                       SimpleScheduler.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
                   }
                   
      @@ -701,9 +693,7 @@ class PeerTestManager {
                   _nonce = nonce;
               }
               public void timeReached() {
      -            synchronized (_activeTests) {
                       _activeTests.remove(new Long(_nonce));
      -            }
               }
           }
       }
      diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestState.java b/router/java/src/net/i2p/router/transport/udp/PeerTestState.java
      index 16fff5fa8..b5219476c 100644
      --- a/router/java/src/net/i2p/router/transport/udp/PeerTestState.java
      +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestState.java
      @@ -33,71 +33,71 @@ class PeerTestState {
           public static final short BOB = 2;
           public static final short CHARLIE = 3;
           
      -    public synchronized long getNonce() { return _testNonce; }
      -    public synchronized void setNonce(long nonce) { _testNonce = nonce; }
      +    public long getNonce() { return _testNonce; }
      +    public void setNonce(long nonce) { _testNonce = nonce; }
           /** Are we Alice, bob, or Charlie. */
      -    public synchronized short getOurRole() { return _ourRole; }
      -    public synchronized void setOurRole(short role) { _ourRole = role; }
      +    public short getOurRole() { return _ourRole; }
      +    public void setOurRole(short role) { _ourRole = role; }
           /**
            * If we are Alice, this will contain the IP that Bob says we
            * can be reached at - the IP Charlie says we can be reached 
            * at is _aliceIPFromCharlie
            *
            */
      -    public synchronized InetAddress getAliceIP() { return _aliceIP; }
      -    public synchronized void setAliceIP(InetAddress ip) { _aliceIP = ip; }
      -    public synchronized InetAddress getBobIP() { return _bobIP; }
      -    public synchronized void setBobIP(InetAddress ip) { _bobIP = ip; }
      -    public synchronized InetAddress getCharlieIP() { return _charlieIP; }
      -    public synchronized void setCharlieIP(InetAddress ip) { _charlieIP = ip; }
      -    public synchronized InetAddress getAliceIPFromCharlie() { return _aliceIPFromCharlie; }
      -    public synchronized void setAliceIPFromCharlie(InetAddress ip) { _aliceIPFromCharlie = ip; }
      +    public InetAddress getAliceIP() { return _aliceIP; }
      +    public void setAliceIP(InetAddress ip) { _aliceIP = ip; }
      +    public InetAddress getBobIP() { return _bobIP; }
      +    public void setBobIP(InetAddress ip) { _bobIP = ip; }
      +    public InetAddress getCharlieIP() { return _charlieIP; }
      +    public void setCharlieIP(InetAddress ip) { _charlieIP = ip; }
      +    public InetAddress getAliceIPFromCharlie() { return _aliceIPFromCharlie; }
      +    public void setAliceIPFromCharlie(InetAddress ip) { _aliceIPFromCharlie = ip; }
           /**
            * If we are Alice, this will contain the port that Bob says we
            * can be reached at - the port Charlie says we can be reached
            * at is _alicePortFromCharlie
            *
            */
      -    public synchronized int getAlicePort() { return _alicePort; }
      -    public synchronized void setAlicePort(int alicePort) { _alicePort = alicePort; }
      -    public synchronized int getBobPort() { return _bobPort; }
      -    public synchronized void setBobPort(int bobPort) { _bobPort = bobPort; }
      -    public synchronized int getCharliePort() { return _charliePort; }
      -    public synchronized void setCharliePort(int charliePort) { _charliePort = charliePort; }
      +    public int getAlicePort() { return _alicePort; }
      +    public void setAlicePort(int alicePort) { _alicePort = alicePort; }
      +    public int getBobPort() { return _bobPort; }
      +    public void setBobPort(int bobPort) { _bobPort = bobPort; }
      +    public int getCharliePort() { return _charliePort; }
      +    public void setCharliePort(int charliePort) { _charliePort = charliePort; }
           
      -    public synchronized int getAlicePortFromCharlie() { return _alicePortFromCharlie; }
      -    public synchronized void setAlicePortFromCharlie(int alicePortFromCharlie) { _alicePortFromCharlie = alicePortFromCharlie; }
      +    public int getAlicePortFromCharlie() { return _alicePortFromCharlie; }
      +    public void setAlicePortFromCharlie(int alicePortFromCharlie) { _alicePortFromCharlie = alicePortFromCharlie; }
           
      -    public synchronized SessionKey getAliceIntroKey() { return _aliceIntroKey; }
      -    public synchronized void setAliceIntroKey(SessionKey key) { _aliceIntroKey = key; }
      -    public synchronized SessionKey getCharlieIntroKey() { return _charlieIntroKey; }
      -    public synchronized void setCharlieIntroKey(SessionKey key) { _charlieIntroKey = key; }
      -    public synchronized SessionKey getBobCipherKey() { return _bobCipherKey; }
      -    public synchronized void setBobCipherKey(SessionKey key) { _bobCipherKey = key; }
      -    public synchronized SessionKey getBobMACKey() { return _bobMACKey; }
      -    public synchronized void setBobMACKey(SessionKey key) { _bobMACKey = key; }
      +    public SessionKey getAliceIntroKey() { return _aliceIntroKey; }
      +    public void setAliceIntroKey(SessionKey key) { _aliceIntroKey = key; }
      +    public SessionKey getCharlieIntroKey() { return _charlieIntroKey; }
      +    public void setCharlieIntroKey(SessionKey key) { _charlieIntroKey = key; }
      +    public SessionKey getBobCipherKey() { return _bobCipherKey; }
      +    public void setBobCipherKey(SessionKey key) { _bobCipherKey = key; }
      +    public SessionKey getBobMACKey() { return _bobMACKey; }
      +    public void setBobMACKey(SessionKey key) { _bobMACKey = key; }
           
           /** when did this test begin? */
      -    public synchronized long getBeginTime() { return _beginTime; }
      -    public synchronized void setBeginTime(long when) { _beginTime = when; }
      +    public long getBeginTime() { return _beginTime; }
      +    public void setBeginTime(long when) { _beginTime = when; }
           /** when did we last send out a packet? */
      -    public synchronized long getLastSendTime() { return _lastSendTime; }
      -    public synchronized void setLastSendTime(long when) { _lastSendTime = when; }
      +    public long getLastSendTime() { return _lastSendTime; }
      +    public void setLastSendTime(long when) { _lastSendTime = when; }
           /** when did we last hear from alice? */
      -    public synchronized long getReceiveAliceTime() { return _receiveAliceTime; }
      -    public synchronized void setReceiveAliceTime(long when) { _receiveAliceTime = when; }
      +    public long getReceiveAliceTime() { return _receiveAliceTime; }
      +    public void setReceiveAliceTime(long when) { _receiveAliceTime = when; }
           /** when did we last hear from bob? */
      -    public synchronized long getReceiveBobTime() { return _receiveBobTime; }
      -    public synchronized void setReceiveBobTime(long when) { _receiveBobTime = when; }
      +    public long getReceiveBobTime() { return _receiveBobTime; }
      +    public void setReceiveBobTime(long when) { _receiveBobTime = when; }
           /** when did we last hear from charlie? */
      -    public synchronized long getReceiveCharlieTime() { return _receiveCharlieTime; }
      -    public synchronized void setReceiveCharlieTime(long when) { _receiveCharlieTime = when; }
      +    public long getReceiveCharlieTime() { return _receiveCharlieTime; }
      +    public void setReceiveCharlieTime(long when) { _receiveCharlieTime = when; }
           
           public int getPacketsRelayed() { return _packetsRelayed; }
           public void incrementPacketsRelayed() { ++_packetsRelayed; }
           
           @Override
      -    public synchronized String toString() {
      +    public String toString() {
               StringBuilder buf = new StringBuilder(512);
               buf.append("Role: ");
               if (_ourRole == ALICE) buf.append("Alice");
      diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
      index 97e112df3..5dc001e18 100644
      --- a/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
      +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacket.java
      @@ -2,9 +2,9 @@ package net.i2p.router.transport.udp;
       
       import java.net.DatagramPacket;
       import java.net.InetAddress;
      -import java.util.ArrayList;
       import java.util.Arrays;
      -import java.util.List;
      +import java.util.Queue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.I2PAppContext;
       import net.i2p.data.DataHelper;
      @@ -40,15 +40,17 @@ public class UDPPacket {
           private int _validateCount;
           // private boolean _isInbound;
         
      -    private static final List _packetCache;
      +    private static final Queue _packetCache;
      +    private static final boolean CACHE = true;
      +    private static final int CACHE_SIZE = 64;
           static {
      -        _packetCache = new ArrayList(256);
      +        if (CACHE)
      +            _packetCache = new LinkedBlockingQueue(CACHE_SIZE);
      +        else
      +            _packetCache = null;
               _log = I2PAppContext.getGlobalContext().logManager().getLog(UDPPacket.class);
           }
           
      -    private static final boolean CACHE = true; // TODO: support caching to cut churn down a /lot/
      -    private static final int CACHE_SIZE = 64;
      -      
           static final int MAX_PACKET_SIZE = 2048;
           public static final int IV_SIZE = 16;
           public static final int MAC_SIZE = 16;
      @@ -121,7 +123,9 @@ public class UDPPacket {
           
           private int _messageType;
           private int _fragmentCount;
      +    /** only for debugging and stats, does not go on the wire */
           int getMessageType() { return _messageType; }
      +    /** only for debugging and stats, does not go on the wire */
           void setMessageType(int type) { _messageType = type; }
           int getFragmentCount() { return _fragmentCount; }
           void setFragmentCount(int count) { _fragmentCount = count; }
      @@ -238,7 +242,7 @@ public class UDPPacket {
           @Override
           public String toString() {
               verifyNotReleased(); 
      -        StringBuilder buf = new StringBuilder(64);
      +        StringBuilder buf = new StringBuilder(256);
               buf.append(_packet.getLength());
               buf.append(" byte packet with ");
               buf.append(_packet.getAddress().getHostAddress()).append(":");
      @@ -256,12 +260,7 @@ public class UDPPacket {
           public static UDPPacket acquire(I2PAppContext ctx, boolean inbound) {
               UDPPacket rv = null;
               if (CACHE) {
      -            synchronized (_packetCache) {
      -                if (_packetCache.size() > 0) {
      -                    rv = (UDPPacket)_packetCache.remove(0);
      -                }
      -            }
      -            
      +            rv = _packetCache.poll();
                   if (rv != null)
                       rv.init(ctx, inbound);
               }
      @@ -284,11 +283,7 @@ public class UDPPacket {
               //_dataCache.release(_dataBuf);
               if (!CACHE)
                   return;
      -        synchronized (_packetCache) {
      -            if (_packetCache.size() <= CACHE_SIZE) {
      -                _packetCache.add(this);
      -            }
      -        }
      +        _packetCache.offer(this);
           }
           
           private void verifyNotReleased() {
      diff --git a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
      index efb85ac48..395a2fcf0 100644
      --- a/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
      +++ b/router/java/src/net/i2p/router/transport/udp/UDPPacketReader.java
      @@ -402,7 +402,7 @@ public class UDPPacketReader {
               
               @Override
               public String toString() {
      -            StringBuilder buf = new StringBuilder(256);
      +            StringBuilder buf = new StringBuilder(512);
                   long msAgo = _context.clock().now() - readTimestamp()*1000;
                   buf.append("Data packet sent ").append(msAgo).append("ms ago ");
                   buf.append("IV ");
      diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
      index c1bd5133a..88cb20779 100644
      --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
      +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
      @@ -2,8 +2,8 @@ package net.i2p.router.transport.udp;
       
       import java.io.IOException;
       import java.net.DatagramSocket;
      -import java.util.ArrayList;
      -import java.util.List;
      +import java.util.concurrent.BlockingQueue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.router.RouterContext;
       import net.i2p.router.transport.FIFOBandwidthLimiter;
      @@ -24,19 +24,20 @@ public class UDPReceiver {
           private Log _log;
           private DatagramSocket _socket;
           private String _name;
      -    private final List _inboundQueue;
      +    private final BlockingQueue _inboundQueue;
           private boolean _keepRunning;
           private Runner _runner;
           private UDPTransport _transport;
      -    // private static int __id;
      +    private static int __id;
           private int _id;
      +    private static final int TYPE_POISON = -99999;
           
           public UDPReceiver(RouterContext ctx, UDPTransport transport, DatagramSocket socket, String name) {
               _context = ctx;
               _log = ctx.logManager().getLog(UDPReceiver.class);
      -        _id++;
      +        _id = ++__id;
               _name = name;
      -        _inboundQueue = new ArrayList(128);
      +        _inboundQueue = new LinkedBlockingQueue();
               _socket = socket;
               _transport = transport;
               _runner = new Runner();
      @@ -50,17 +51,22 @@ public class UDPReceiver {
           public void startup() {
               //adjustDropProbability();
               _keepRunning = true;
      -        I2PThread t = new I2PThread(_runner, _name + "." + _id);
      -        t.setDaemon(true);
      +        I2PThread t = new I2PThread(_runner, _name + '.' + _id, true);
               t.start();
           }
           
           public void shutdown() {
               _keepRunning = false;
      -        synchronized (_inboundQueue) {
      -            _inboundQueue.clear();
      -            _inboundQueue.notifyAll();
      +        _inboundQueue.clear();
      +        UDPPacket poison = UDPPacket.acquire(_context, false);
      +        poison.setMessageType(TYPE_POISON);
      +        _inboundQueue.offer(poison);
      +        for (int i = 1; i <= 5 && !_inboundQueue.isEmpty(); i++) {
      +            try {
      +                Thread.sleep(i * 50);
      +            } catch (InterruptedException ie) {}
               }
      +        _inboundQueue.clear();
           }
           
       /*********
      @@ -96,6 +102,7 @@ public class UDPReceiver {
           private static final int ARTIFICIAL_DELAY_BASE = 0; //600;
       **********/
           
      +    /** @return zero (was queue size) */
           private int receive(UDPPacket packet) {
       /*********
               //adjustDropProbability();
      @@ -126,7 +133,12 @@ public class UDPReceiver {
               
               return doReceive(packet);
           }
      +
      +    /** @return zero (was queue size) */
           private final int doReceive(UDPPacket packet) {
      +        if (!_keepRunning)
      +            return 0;
      +
               if (_log.shouldLog(Log.INFO))
                   _log.info("Received: " + packet);
               
      @@ -143,26 +155,25 @@ public class UDPReceiver {
               boolean rejected = false;
               int queueSize = 0;
               long headPeriod = 0;
      -        synchronized (_inboundQueue) {
      -            queueSize = _inboundQueue.size();
      -            if (queueSize > 0) {
      -                headPeriod = ((UDPPacket)_inboundQueue.get(0)).getLifetime();
      +
      +            UDPPacket head = _inboundQueue.peek();
      +            if (head != null) {
      +                headPeriod = head.getLifetime();
                       if (headPeriod > MAX_QUEUE_PERIOD) {
                           rejected = true;
      -                    _inboundQueue.notifyAll();
                       }
                   }
                   if (!rejected) {
      -                _inboundQueue.add(packet);
      -                _inboundQueue.notifyAll();
      -                return queueSize + 1;
      +                _inboundQueue.offer(packet);
      +                //return queueSize + 1;
      +                return 0;
                   }
      -        }
               
               // rejected
               packet.release();
               _context.statManager().addRateData("udp.droppedInbound", queueSize, headPeriod);
               if (_log.shouldLog(Log.WARN)) {
      +            queueSize = _inboundQueue.size();
                   StringBuilder msg = new StringBuilder();
                   msg.append("Dropping inbound packet with ");
                   msg.append(queueSize);
      @@ -188,21 +199,15 @@ public class UDPReceiver {
            */
           public UDPPacket receiveNext() {
               UDPPacket rv = null;
      -        int remaining = 0;
      -        while (_keepRunning) {
      -            synchronized (_inboundQueue) {
      -                if (_inboundQueue.size() <= 0)
      -                    try { _inboundQueue.wait(); } catch (InterruptedException ie) {}
      -                if (_inboundQueue.size() > 0) {
      -                    rv = (UDPPacket)_inboundQueue.remove(0);
      -                    remaining = _inboundQueue.size();
      -                    if (remaining > 0)
      -                        _inboundQueue.notifyAll();
      -                    break;
      -                }
      -            }
      +        //int remaining = 0;
      +        while (_keepRunning && rv == null) {
      +            try {
      +                rv = _inboundQueue.take();
      +            } catch (InterruptedException ie) {}
      +            if (rv != null && rv.getMessageType() == TYPE_POISON)
      +                return null;
               }
      -        _context.statManager().addRateData("udp.receiveRemaining", remaining, 0);
      +        //_context.statManager().addRateData("udp.receiveRemaining", remaining, 0);
               return rv;
           }
           
      diff --git a/router/java/src/net/i2p/router/transport/udp/UDPSender.java b/router/java/src/net/i2p/router/transport/udp/UDPSender.java
      index 446fd9d63..6d1cd7c3a 100644
      --- a/router/java/src/net/i2p/router/transport/udp/UDPSender.java
      +++ b/router/java/src/net/i2p/router/transport/udp/UDPSender.java
      @@ -3,8 +3,8 @@ package net.i2p.router.transport.udp;
       import java.io.IOException;
       import java.net.DatagramPacket;
       import java.net.DatagramSocket;
      -import java.util.ArrayList;
      -import java.util.List;
      +import java.util.concurrent.BlockingQueue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.router.RouterContext;
       import net.i2p.router.transport.FIFOBandwidthLimiter;
      @@ -20,16 +20,17 @@ public class UDPSender {
           private Log _log;
           private DatagramSocket _socket;
           private String _name;
      -    private final List _outboundQueue;
      +    private final BlockingQueue _outboundQueue;
           private boolean _keepRunning;
           private Runner _runner;
      +    private static final int TYPE_POISON = 99999;
           
      -    private static final int MAX_QUEUED = 4;
      +    //private static final int MAX_QUEUED = 4;
           
           public UDPSender(RouterContext ctx, DatagramSocket socket, String name) {
               _context = ctx;
               _log = ctx.logManager().getLog(UDPSender.class);
      -        _outboundQueue = new ArrayList(128);
      +        _outboundQueue = new LinkedBlockingQueue();
               _socket = socket;
               _runner = new Runner();
               _name = name;
      @@ -44,49 +45,40 @@ public class UDPSender {
               // used in RouterWatchdog
               _context.statManager().createRateStat("udp.sendException", "How frequently we fail to send a packet (likely due to a windows exception)", "udp", new long[] { 60*1000, 10*60*1000 });
       
      -        _context.statManager().createRateStat("udp.sendPacketSize.1", "db store message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.2", "db lookup message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.3", "db search reply message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.6", "tunnel create message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.7", "tunnel create status message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.10", "delivery status message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.11", "garlic message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.16", "date message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.18", "tunnel data message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.19", "tunnel gateway message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.20", "data message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.21", "tunnel build", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.22", "tunnel build reply", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.20", "data message size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.42", "ack-only packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.43", "hole punch packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.44", "relay response packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.45", "relay intro packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.46", "relay request packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.47", "peer test charlie to bob packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.48", "peer test bob to charlie packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.49", "peer test to alice packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.50", "peer test from alice packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.51", "session confirmed packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.52", "session request packet size", "udp", UDPTransport.RATES);
      -        _context.statManager().createRateStat("udp.sendPacketSize.53", "session created packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_ACK, "ack-only packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_PUNCH, "hole punch packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_RESP, "relay response packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_INTRO, "relay intro packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_RREQ, "relay request packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TCB, "peer test charlie to bob packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TBC, "peer test bob to charlie packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TTA, "peer test to alice packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_TFA, "peer test from alice packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_CONF, "session confirmed packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_SREQ, "session request packet size", "udp", UDPTransport.RATES);
      +        _context.statManager().createRateStat("udp.sendPacketSize." + PacketBuilder.TYPE_CREAT, "session created packet size", "udp", UDPTransport.RATES);
           }
           
           public void startup() {
               if (_log.shouldLog(Log.DEBUG))
                   _log.debug("Starting the runner: " + _name);
               _keepRunning = true;
      -        I2PThread t = new I2PThread(_runner, _name);
      -        t.setDaemon(true);
      +        I2PThread t = new I2PThread(_runner, _name, true);
               t.start();
           }
           
           public void shutdown() {
               _keepRunning = false;
      -        synchronized (_outboundQueue) {
      -            _outboundQueue.clear();
      -            _outboundQueue.notifyAll();
      +        _outboundQueue.clear();
      +        UDPPacket poison = UDPPacket.acquire(_context, false);
      +        poison.setMessageType(TYPE_POISON);
      +        _outboundQueue.offer(poison);
      +        for (int i = 1; i <= 5 && !_outboundQueue.isEmpty(); i++) {
      +            try {
      +                Thread.sleep(i * 50);
      +            } catch (InterruptedException ie) {}
               }
      +        _outboundQueue.clear();
           }
           
           public DatagramSocket updateListeningPort(DatagramSocket socket, int newPort) {
      @@ -98,10 +90,12 @@ public class UDPSender {
            * Add the packet to the queue.  This may block until there is space
            * available, if requested, otherwise it returns immediately
            *
      -     * @param blockTime how long to block
      +     * @param blockTime how long to block IGNORED
            * @return number of packets queued
      +     * @deprecated use add(packet)
            */
           public int add(UDPPacket packet, int blockTime) {
      +     /********
               //long expiration = _context.clock().now() + blockTime;
               int remaining = -1;
               long lifetime = -1;
      @@ -124,13 +118,12 @@ public class UDPSender {
                               }
                           }
                           
      -                    //if (true || (_outboundQueue.size() < MAX_QUEUED)) {
      +                    if (true || (_outboundQueue.size() < MAX_QUEUED)) {
                               lifetime = packet.getLifetime();
                               _outboundQueue.add(packet);
                               added = true;
                               remaining = _outboundQueue.size();
                               _outboundQueue.notifyAll();
      -                    /*****
                           } else {
                               long remainingTime = expiration - _context.clock().now();
                               if (remainingTime > 0) {
      @@ -141,7 +134,6 @@ public class UDPSender {
                               }
                               lifetime = packet.getLifetime();
                           }
      -                    *****/
                       }
                   //} catch (InterruptedException ie) {}
               }
      @@ -153,42 +145,26 @@ public class UDPSender {
               if (_log.shouldLog(Log.DEBUG))
                   _log.debug("Added the packet onto the queue with " + remaining + " remaining and a lifetime of " + lifetime);
               return remaining;
      +     ********/
      +        return add(packet);
           }
           
           private static final int MAX_HEAD_LIFETIME = 1000;
           
           /**
      -     *
      -     * @return number of packets in the queue
      +     * Put it on the queue
      +     * @return ZERO (used to be number of packets in the queue)
            */
           public int add(UDPPacket packet) {
      -        if (packet == null) return 0;
      +        if (packet == null || !_keepRunning) return 0;
               int size = 0;
      -        long lifetime = -1;
      -        int removed = 0;
      -        synchronized (_outboundQueue) {
      -            lifetime = packet.getLifetime();
      -            UDPPacket head = null;
      -            if (_outboundQueue.size() > 0) {
      -                head = (UDPPacket)_outboundQueue.get(0);
      -                while (head.getLifetime() > MAX_HEAD_LIFETIME) {
      -                    _outboundQueue.remove(0);
      -                    removed++;
      -                    if (_outboundQueue.size() > 0)
      -                        head = (UDPPacket)_outboundQueue.get(0);
      -                    else
      -                        break;
      -                }
      -            }
      -            _outboundQueue.add(packet);
      +        _outboundQueue.offer(packet);
      +        //size = _outboundQueue.size();
      +        //_context.statManager().addRateData("udp.sendQueueSize", size, lifetime);
      +        if (_log.shouldLog(Log.DEBUG)) {
                   size = _outboundQueue.size();
      -            _outboundQueue.notifyAll();
      +            _log.debug("Added the packet onto the queue with " + size + " remaining and a lifetime of " + packet.getLifetime());
               }
      -        _context.statManager().addRateData("udp.sendQueueSize", size, lifetime);
      -        if (removed > 0)
      -            _context.statManager().addRateData("udp.sendQueueTrimmed", removed, size);
      -        if (_log.shouldLog(Log.DEBUG))
      -            _log.debug("Added the packet onto the queue with " + size + " remaining and a lifetime of " + lifetime);
               return size;
           }
           
      @@ -227,7 +203,8 @@ public class UDPSender {
                               //_log.debug("Sending packet: (size="+size + "/"+size2 +")\nraw: " + Base64.encode(packet.getPacket().getData(), 0, size));
                           }
                           
      -                    _context.statManager().addRateData("udp.sendPacketSize." + packet.getMessageType(), size, packet.getFragmentCount());
      +                    if (packet.getMessageType() >= PacketBuilder.TYPE_FIRST)
      +                        _context.statManager().addRateData("udp.sendPacketSize." + packet.getMessageType(), size, packet.getFragmentCount());
                           
                           //packet.getPacket().setLength(size);
                           try {
      @@ -267,20 +244,17 @@ public class UDPSender {
                       _log.debug("Stop sending...");
               }
               
      +        /** @return next packet in queue. Will discard any packet older than MAX_HEAD_LIFETIME */
               private UDPPacket getNextPacket() {
                   UDPPacket packet = null;
      -            while ( (_keepRunning) && (packet == null) ) {
      +            while ( (_keepRunning) && (packet == null || packet.getLifetime() > MAX_HEAD_LIFETIME) ) {
      +                if (packet != null)
      +                    _context.statManager().addRateData("udp.sendQueueTrimmed", 1, 0);
                       try {
      -                    synchronized (_outboundQueue) {
      -                        if (_outboundQueue.size() <= 0) {
      -                            _outboundQueue.notifyAll();
      -                            _outboundQueue.wait();
      -                        } else {
      -                            packet = (UDPPacket)_outboundQueue.remove(0);
      -                            _outboundQueue.notifyAll();
      -                        }
      -                    }
      +                    packet = _outboundQueue.take();
                       } catch (InterruptedException ie) {}
      +                if (packet != null && packet.getMessageType() == TYPE_POISON)
      +                    return null;
                   }
                   return packet;
               }
      diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
      index 853d8c18a..fd0e5c9e4 100644
      --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
      +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
      @@ -13,8 +13,10 @@ import java.util.Iterator;
       import java.util.List;
       import java.util.Map;
       import java.util.Properties;
      +import java.util.Set;
       import java.util.TreeSet;
       import java.util.Vector;
      +import java.util.concurrent.ConcurrentHashMap;
       
       import net.i2p.data.DataHelper;
       import net.i2p.data.Hash;
      @@ -31,6 +33,7 @@ import net.i2p.router.RouterContext;
       import net.i2p.router.transport.Transport;
       import net.i2p.router.transport.TransportBid;
       import net.i2p.router.transport.TransportImpl;
      +import net.i2p.util.ConcurrentHashSet;
       import net.i2p.util.Log;
       import net.i2p.util.SimpleScheduler;
       import net.i2p.util.SimpleTimer;
      @@ -75,8 +78,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
           /** introduction key */
           private SessionKey _introKey;
           
      -    /** list of RemoteHostId for peers whose packets we want to drop outright */
      -    private final List _dropList;
      +    /**
      +     *  List of RemoteHostId for peers whose packets we want to drop outright
      +     *  This is only for old network IDs (pre-0.6.1.10), so it isn't really used now.
      +     */
      +    private final Set _dropList;
           
           private int _expireTimeout;
       
      @@ -167,9 +173,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
               super(ctx);
               _context = ctx;
               _log = ctx.logManager().getLog(UDPTransport.class);
      -        _peersByIdent = new HashMap(128);
      -        _peersByRemoteHost = new HashMap(128);
      -        _dropList = new ArrayList(256);
      +        _peersByIdent = new ConcurrentHashMap(128);
      +        _peersByRemoteHost = new ConcurrentHashMap(128);
      +        _dropList = new ConcurrentHashSet(2);
               _endpoint = null;
               
               // See comments in DQAT.java
      @@ -608,9 +614,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
            * if no state exists
            */
           PeerState getPeerState(RemoteHostId hostInfo) {
      -        synchronized (_peersByRemoteHost) {
                   return _peersByRemoteHost.get(hostInfo);
      -        }
           }
           
           /** 
      @@ -618,9 +622,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
            * if no state exists
            */
           public PeerState getPeerState(Hash remotePeer) { 
      -        synchronized (_peersByIdent) {
                   return _peersByIdent.get(remotePeer);
      -        }
           }
           
           /**
      @@ -697,14 +699,12 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
               long oldEstablishedOn = -1;
               PeerState oldPeer = null;
               if (remotePeer != null) {
      -            synchronized (_peersByIdent) {
                       oldPeer = _peersByIdent.put(remotePeer, peer);
                       if ( (oldPeer != null) && (oldPeer != peer) ) {
                           // transfer over the old state/inbound message fragments/etc
                           peer.loadFrom(oldPeer);
                           oldEstablishedOn = oldPeer.getKeyEstablishedTime();
                       }
      -            }
               }
               
               if (oldPeer != null) {
      @@ -717,13 +717,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
               RemoteHostId remoteId = peer.getRemoteHostId();
               if (remoteId == null) return false;
               
      -        synchronized (_peersByRemoteHost) {
                   oldPeer = _peersByRemoteHost.put(remoteId, peer);
                   if ( (oldPeer != null) && (oldPeer != peer) ) {
                       // transfer over the old state/inbound message fragments/etc
                       peer.loadFrom(oldPeer);
                       oldEstablishedOn = oldPeer.getKeyEstablishedTime();
      -            }
               }
               
               if (oldPeer != null) {
      @@ -773,6 +771,8 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                   DatabaseStoreMessage dsm = (DatabaseStoreMessage)inMsg;
                   if ( (dsm.getRouterInfo() != null) && 
                        (dsm.getRouterInfo().getNetworkId() != Router.NETWORK_ID) ) {
      +                // this is pre-0.6.1.10, so it isn't going to happen any more
      +
                       /*
                       if (remoteIdentHash != null) {
                           _context.shitlist().shitlistRouter(remoteIdentHash, "Sent us a peer from the wrong network");
      @@ -792,21 +792,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                       PeerState peer = getPeerState(peerHash);
                       if (peer != null) {
                           RemoteHostId remote = peer.getRemoteHostId();
      -                    boolean added = false;
      -                    int droplistSize = 0;
      -                    synchronized (_dropList) {
      -                        if (!_dropList.contains(remote)) {
      -                            while (_dropList.size() > MAX_DROPLIST_SIZE)
      -                                _dropList.remove(0);
      -                            _dropList.add(remote);
      -                            added = true;
      -                        }
      -                        droplistSize = _dropList.size();
      -                    }
      -                    if (added) {
      -                        _context.statManager().addRateData("udp.dropPeerDroplist", droplistSize, 0);
      -                        SimpleScheduler.getInstance().addEvent(new RemoveDropList(remote), DROPLIST_PERIOD);
      -                    }
      +                    _dropList.add(remote);
      +                    _context.statManager().addRateData("udp.dropPeerDroplist", 1, 0);
      +                    SimpleScheduler.getInstance().addEvent(new RemoveDropList(remote), DROPLIST_PERIOD);
                       }
                       markUnreachable(peerHash);
                       _context.shitlist().shitlistRouter(peerHash, "Part of the wrong network, version = " + dsm.getRouterInfo().getOption("router.version"));
      @@ -838,13 +826,11 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
               private RemoteHostId _peer;
               public RemoveDropList(RemoteHostId peer) { _peer = peer; }
               public void timeReached() { 
      -            synchronized (_dropList) {
      -                _dropList.remove(_peer);
      -            }
      +            _dropList.remove(_peer);
               }
           }
           
      -    boolean isInDropList(RemoteHostId peer) { synchronized (_dropList) { return _dropList.contains(peer); } }
      +    boolean isInDropList(RemoteHostId peer) { return _dropList.contains(peer); }
           
           void dropPeer(Hash peer, boolean shouldShitlist, String why) {
               PeerState state = getPeerState(peer);
      @@ -916,16 +902,12 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                   }
                   long now = _context.clock().now();
                   _context.statManager().addRateData("udp.droppedPeer", now - peer.getLastReceiveTime(), now - peer.getKeyEstablishedTime());
      -            synchronized (_peersByIdent) {
      -                altByIdent = _peersByIdent.remove(peer.getRemotePeer());
      -            }
      +            altByIdent = _peersByIdent.remove(peer.getRemotePeer());
               }
               
               RemoteHostId remoteId = peer.getRemoteHostId();
               if (remoteId != null) {
      -            synchronized (_peersByRemoteHost) {
                       altByHost = _peersByRemoteHost.remove(remoteId);
      -            }
               }
               
               // unchoke 'em, but just because we'll never talk again...
      @@ -1087,10 +1069,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                   // also introduce us, also bid aggressively so we are preferred over NTCP.
                   // (Otherwise we only talk UDP to those that are firewalled, and we will
                   // never get any introducers)
      -            int count;
      -            synchronized (_peersByIdent) {
      -                count = _peersByIdent.size();
      -            }
      +            int count = _peersByIdent.size();
                   if (alwaysPreferUDP() || count < MIN_PEERS ||
                       (introducersRequired() && _introManager.introducerCount() < MIN_INTRODUCER_POOL))
                       return _cachedBid[SLOW_PREFERRED_BID];
      @@ -1474,9 +1453,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
       
           @Override
           public int countPeers() {
      -        synchronized (_peersByIdent) {
                   return _peersByIdent.size();
      -        }
           }
       
           @Override
      @@ -1484,7 +1461,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
               long now = _context.clock().now();
               int active = 0;
               int inactive = 0;
      -        synchronized (_peersByIdent) {
                   for (Iterator iter = _peersByIdent.values().iterator(); iter.hasNext(); ) {
                       PeerState peer = iter.next();
                       if (now-peer.getLastReceiveTime() > 5*60*1000)
      @@ -1492,7 +1468,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                       else
                           active++;
                   }
      -        }
               return active;
           }
           
      @@ -1501,7 +1476,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
               long now = _context.clock().now();
               int active = 0;
               int inactive = 0;
      -        synchronized (_peersByIdent) {
                   for (Iterator iter = _peersByIdent.values().iterator(); iter.hasNext(); ) {
                       PeerState peer = iter.next();
                       if (now-peer.getLastSendFullyTime() > 1*60*1000)
      @@ -1509,7 +1483,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                       else
                           active++;
                   }
      -        }
               return active;
           }
           
      @@ -1519,9 +1492,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
           }
       
           public boolean allowConnection() {
      -        synchronized (_peersByIdent) {
                   return _peersByIdent.size() < getMaxConnections();
      -        }
           }
       
           /**
      @@ -1534,9 +1505,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
               Vector skews = new Vector();
               Vector peers = new Vector();
       
      -        synchronized (_peersByIdent) {
      -            peers.addAll(_peersByIdent.values());
      -        }
      +        peers.addAll(_peersByIdent.values());
       
               // If our clock is way off, we may not have many (or any) successful connections,
               // so try hard in that case to return good data
      @@ -1557,15 +1526,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
           /** **internal, do not use** */
           public static final UDPTransport _instance() { return __instance; }
           /** **internal, do not use** return the peers (Hash) of active peers. */
      -    public List _getActivePeers() {
      -        List peers = new ArrayList(128);
      -        synchronized (_peersByIdent) {
      -            peers.addAll(_peersByIdent.keySet());
      -        }
      +    public List _getActivePeers() {
      +        List peers = new ArrayList(128);
      +        peers.addAll(_peersByIdent.keySet());
               
               long now = _context.clock().now();
      -        for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
      -            Hash peer = (Hash)iter.next();
      +        for (Iterator iter = peers.iterator(); iter.hasNext(); ) {
      +            Hash peer = iter.next();
                   PeerState state = getPeerState(peer);
                   if (now-state.getLastReceiveTime() > 5*60*1000)
                       iter.remove(); // don't include old peers
      @@ -1886,9 +1853,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
           @Override
           public void renderStatusHTML(Writer out, String urlBase, int sortFlags) throws IOException {
               TreeSet peers = new TreeSet(getComparator(sortFlags));
      -        synchronized (_peersByIdent) {
      -            peers.addAll(_peersByIdent.values());
      -        }
      +        peers.addAll(_peersByIdent.values());
               long offsetTotal = 0;
       
               int bpsIn = 0;
      @@ -2205,12 +2170,12 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
           }
           
           private class ExpirePeerEvent implements SimpleTimer.TimedEvent {
      -        private final List _expirePeers;
      -        private List _expireBuffer;
      +        private final Set _expirePeers;
      +        private final List _expireBuffer;
               private boolean _alive;
               public ExpirePeerEvent() {
      -            _expirePeers = new ArrayList(128);
      -            _expireBuffer = new ArrayList(128);
      +            _expirePeers = new ConcurrentHashSet(128);
      +            _expireBuffer = new ArrayList();
               }
               public void timeReached() {
                   // Increase allowed idle time if we are well under allowed connections, otherwise decrease
      @@ -2222,10 +2187,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                   long longInactivityCutoff = _context.clock().now() - EXPIRE_TIMEOUT;
                   long pingCutoff = _context.clock().now() - (2 * 60*60*1000);
                   _expireBuffer.clear();
      -            synchronized (_expirePeers) {
      -                int sz = _expirePeers.size();
      -                for (int i = 0; i < sz; i++) {
      -                    PeerState peer = (PeerState)_expirePeers.get(i);
      +
      +                for (Iterator iter = _expirePeers.iterator(); iter.hasNext(); ) {
      +                    PeerState peer = iter.next();
                           long inactivityCutoff;
                           // if we offered to introduce them, or we used them as introducer in last 2 hours
                           if (peer.getWeRelayToThemAs() > 0 || peer.getIntroducerTime() > pingCutoff)
      @@ -2234,28 +2198,22 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                               inactivityCutoff = shortInactivityCutoff;
                           if ( (peer.getLastReceiveTime() < inactivityCutoff) && (peer.getLastSendTime() < inactivityCutoff) ) {
                               _expireBuffer.add(peer);
      -                        _expirePeers.remove(i);
      -                        i--;
      -                        sz--;
      +                        iter.remove();
                           }
                       }
      -            }
      +
                   for (int i = 0; i < _expireBuffer.size(); i++)
      -                dropPeer((PeerState)_expireBuffer.get(i), false, "idle too long");
      +                dropPeer(_expireBuffer.get(i), false, "idle too long");
                   _expireBuffer.clear();
       
                   if (_alive)
                       SimpleTimer.getInstance().addEvent(ExpirePeerEvent.this, 30*1000);
               }
               public void add(PeerState peer) {
      -            synchronized (_expirePeers) {
                       _expirePeers.add(peer);
      -            }
               }
               public void remove(PeerState peer) {
      -            synchronized (_expirePeers) {
                       _expirePeers.remove(peer);
      -            }
               }
               public void setIsAlive(boolean isAlive) {
                   _alive = isAlive;
      @@ -2263,9 +2221,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                       SimpleTimer.getInstance().addEvent(ExpirePeerEvent.this, 30*1000);
                   } else {
                       SimpleTimer.getInstance().removeEvent(ExpirePeerEvent.this);
      -                synchronized (_expirePeers) {
      -                    _expirePeers.clear();
      -                }
      +                _expirePeers.clear();
                   }
               }
           }
      @@ -2348,10 +2304,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
           }
           
           PeerState pickTestPeer(RemoteHostId dontInclude) {
      -        List peers = null;
      -        synchronized (_peersByIdent) {
      -            peers = new ArrayList(_peersByIdent.values());
      -        }
      +        List peers = new ArrayList(_peersByIdent.values());
               Collections.shuffle(peers, _context.random());
               for (int i = 0; i < peers.size(); i++) {
                   PeerState peer = peers.get(i);
      diff --git a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
      index 0e32f0f93..3dac4dd20 100644
      --- a/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
      +++ b/router/java/src/net/i2p/router/tunnel/TrivialPreprocessor.java
      @@ -101,7 +101,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
            * @deprecated unused except by above
            */
           private byte[][] preprocess(TunnelGateway.Pending msg) {
      -        List fragments = new ArrayList(1);
      +        List fragments = new ArrayList(1);
       
               while (msg.getOffset() < msg.getData().length) {
                   fragments.add(preprocessFragment(msg));
      @@ -111,7 +111,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
       
               byte rv[][] = new byte[fragments.size()][];
               for (int i = 0; i < fragments.size(); i++)
      -            rv[i] = (byte[])fragments.get(i);
      +            rv[i] = fragments.get(i);
               return rv;
           }
           
      diff --git a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
      index 05721d0fb..7dfbded7d 100644
      --- a/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
      +++ b/router/java/src/net/i2p/router/tunnel/TunnelDispatcher.java
      @@ -386,7 +386,7 @@ public class TunnelDispatcher implements Service {
            *
            */
           public void dispatch(TunnelGatewayMessage msg) {
      -        long before = System.currentTimeMillis();
      +        long before = _context.clock().now();
               TunnelGateway gw = _inboundGateways.get(msg.getTunnelId());
               if (gw != null) {
                   if (_log.shouldLog(Log.DEBUG))
      @@ -423,7 +423,7 @@ public class TunnelDispatcher implements Service {
                                  + " existing = " + _inboundGateways.size(), new Exception("source"));
               }
               
      -        long dispatchTime = System.currentTimeMillis() - before;
      +        long dispatchTime = _context.clock().now() - before;
               
               if (_log.shouldLog(Log.DEBUG))
                   _log.debug("Dispatch in gw time: " + dispatchTime + " gateway? " + gw);
      @@ -726,6 +726,7 @@ public class TunnelDispatcher implements Service {
               startup(); 
           }
           
      +    /** @deprecated moved to router console */
           public void renderStatusHTML(Writer out) throws IOException {}    
           
           private class LeaveTunnel extends JobImpl {
      diff --git a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java
      index 205086472..05db0b0ce 100644
      --- a/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java
      +++ b/router/java/src/net/i2p/router/tunnel/TunnelGatewayPumper.java
      @@ -2,6 +2,8 @@ package net.i2p.router.tunnel;
       
       import java.util.ArrayList;
       import java.util.List;
      +import java.util.concurrent.BlockingQueue;
      +import java.util.concurrent.LinkedBlockingQueue;
       
       import net.i2p.router.RouterContext;
       import net.i2p.util.I2PThread;
      @@ -12,27 +14,36 @@ import net.i2p.util.I2PThread;
        */
       public class TunnelGatewayPumper implements Runnable {
           private RouterContext _context;
      -    private final List _wantsPumping;
      +    private final BlockingQueue _wantsPumping;
           private boolean _stop;
      +    private static final int PUMPERS = 4;
           
           /** Creates a new instance of TunnelGatewayPumper */
           public TunnelGatewayPumper(RouterContext ctx) {
               _context = ctx;
      -        _wantsPumping = new ArrayList(64);
      +        _wantsPumping = new LinkedBlockingQueue();
               _stop = false;
      -        for (int i = 0; i < 4; i++)
      -            new I2PThread(this, "GW pumper " + i, true).start();
      +        for (int i = 0; i < PUMPERS; i++)
      +            new I2PThread(this, "Tunnel GW pumper " + i + '/' + PUMPERS, true).start();
           }
      +
           public void stopPumping() {
               _stop=true;
      -        synchronized (_wantsPumping) { _wantsPumping.notifyAll(); }
      +        _wantsPumping.clear();
      +        PumpedTunnelGateway poison = new PoisonPTG(_context);
      +        for (int i = 0; i < PUMPERS; i++)
      +            _wantsPumping.offer(poison);
      +        for (int i = 1; i <= 5 && !_wantsPumping.isEmpty(); i++) {
      +            try {
      +                Thread.sleep(i * 50);
      +            } catch (InterruptedException ie) {}
      +        }
      +        _wantsPumping.clear();
           }
           
           public void wantsPumping(PumpedTunnelGateway gw) {
      -        synchronized (_wantsPumping) {
      -            _wantsPumping.add(gw);
      -            _wantsPumping.notify();
      -        }
      +        if (!_stop)
      +            _wantsPumping.offer(gw);
           }
           
           public void run() {
      @@ -40,17 +51,25 @@ public class TunnelGatewayPumper implements Runnable {
               List queueBuf = new ArrayList(32);
               while (!_stop) {
                   try {
      -                synchronized (_wantsPumping) {
      -                    if (_wantsPumping.size() > 0)
      -                        gw = _wantsPumping.remove(0);
      -                    else
      -                        _wantsPumping.wait();
      -                }
      +                gw = _wantsPumping.take();
                   } catch (InterruptedException ie) {}
                   if (gw != null) {
      +                if (gw.getMessagesSent() == POISON_PTG)
      +                    break;
                       gw.pump(queueBuf);
                       gw = null;
                   }
               }
           }
      +
      +    private static final int POISON_PTG = -99999;
      +
      +    private static class PoisonPTG extends PumpedTunnelGateway {
      +        public PoisonPTG(RouterContext ctx) {
      +            super(ctx, null, null, null, null);
      +        }
      +
      +        @Override
      +        public int getMessagesSent() { return POISON_PTG; }
      +    }
       }
      diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java
      index 03a7b6ef9..874c2aebc 100644
      --- a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java
      +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java
      @@ -176,7 +176,7 @@ class BuildRequestor {
           
           private static final String MIN_VARIABLE_VERSION = "0.7.12";
           /** change this to true in 0.7.13 if testing goes well */
      -    private static final boolean SEND_VARIABLE = false;
      +    private static final boolean SEND_VARIABLE = true;
           /** 5 (~2600 bytes) fits nicely in 3 tunnel messages */
           private static final int SHORT_RECORDS = 5;
           private static final int LONG_RECORDS = TunnelBuildMessage.MAX_RECORD_COUNT;
      diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java
      index a3b5c4b20..b2343aedb 100644
      --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java
      +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java
      @@ -180,7 +180,8 @@ public abstract class TunnelPeerSelector {
               peers.addAll(ctx.tunnelManager().selectPeersInTooManyTunnels());
               // if (false && filterUnreachable(ctx, isInbound, isExploratory)) {
               if (filterUnreachable(ctx, isInbound, isExploratory)) {
      -            List caps = ctx.peerManager().getPeersByCapability(Router.CAPABILITY_UNREACHABLE);
      +            // This is the only use for getPeersByCapability? And the whole set of datastructures in PeerManager?
      +            List caps = ctx.peerManager().getPeersByCapability(Router.CAPABILITY_UNREACHABLE);
                   if (caps != null)
                       peers.addAll(caps);
                   caps = ctx.profileOrganizer().selectPeersLocallyUnreachable();
      diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageStatus.java b/router/java/test/net/i2p/router/message/OutboundClientMessageStatus.java
      similarity index 100%
      rename from router/java/src/net/i2p/router/message/OutboundClientMessageStatus.java
      rename to router/java/test/net/i2p/router/message/OutboundClientMessageStatus.java