From 21f13dba43c6ab0e1670b99284d5d33379f8e091 Mon Sep 17 00:00:00 2001
From: jrandom <jrandom>
Date: Mon, 21 Feb 2005 18:02:14 +0000
Subject: [PATCH] 2005-02-20  jrandom     * Allow the streaming lib resend
 frequency to drop down to 20s as the       minimum, so that up to 2 retries
 can get sent on an http request.     * Add further limits to failsafe
 tunnels.     * Keep exploratory and client tunnel testing and building stats
 separate.     * Only use the 60s period for throttling tunnel requests due to
 transient       network overload.     * Rebuild tunnels earlier (1-3m before
 expiration, by default)     * Cache the next hop's routerInfo for
 participating tunnels so that the       tunnel participation doesn't depend
 on the netDb.     * Fixed a long standing bug in the streaming lib where we
 wouldn't always       unchoke messages when the window size grows.     * Make
 sure the window size never reaches 0 (duh)

---
 .../net/i2p/i2ptunnel/I2PTunnelRunner.java    |  2 +-
 .../i2p/router/web/ConfigTunnelsHelper.java   |  6 ++--
 .../net/i2p/client/streaming/Connection.java  |  8 ++++-
 .../client/streaming/ConnectionOptions.java   |  2 ++
 .../streaming/ConnectionPacketHandler.java    |  4 +++
 .../streaming/SchedulerConnectedBulk.java     |  2 +-
 .../client/streaming/SchedulerConnecting.java |  2 +-
 .../net/i2p/router/RouterThrottleImpl.java    |  6 ++--
 .../src/net/i2p/router/RouterVersion.java     |  4 +--
 .../net/i2p/router/client/ClientManager.java  |  2 +-
 .../i2p/router/client/RequestLeaseSetJob.java | 10 +++++-
 .../kademlia/SearchUpdateReplyFoundJob.java   | 13 ++++----
 .../i2p/router/tunnel/FragmentHandler.java    |  4 +--
 .../i2p/router/tunnel/TunnelParticipant.java  | 11 ++++++-
 .../i2p/router/tunnel/pool/RebuildJob.java    |  2 +-
 .../router/tunnel/pool/RequestTunnelJob.java  | 24 +++++++++-----
 .../net/i2p/router/tunnel/pool/TestJob.java   |  7 ++++-
 .../i2p/router/tunnel/pool/TunnelBuilder.java |  2 +-
 .../i2p/router/tunnel/pool/TunnelPool.java    | 31 +++++++++++++++++--
 19 files changed, 108 insertions(+), 34 deletions(-)

diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
index fa5a7ce3d2..d99d1b70d0 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java
@@ -216,7 +216,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
             this.out = out;
             _toI2P = toI2P;
             direction = (toI2P ? "toI2P" : "fromI2P");
-            _cache = ByteCache.getInstance(256, NETWORK_BUFFER_SIZE);
+            _cache = ByteCache.getInstance(16, NETWORK_BUFFER_SIZE);
             setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
             start();
         }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java
index 67e817b31f..e59c684176 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHelper.java
@@ -49,9 +49,11 @@ public class ConfigTunnelsHelper {
             TunnelPoolSettings in = _context.tunnelManager().getInboundSettings(dest.calculateHash());
             TunnelPoolSettings out = _context.tunnelManager().getOutboundSettings(dest.calculateHash());
             
-            String name = (in != null ? in.getDestinationNickname() : null);
+            if ( (in == null) || (out == null) ) continue;
+            
+            String name = in.getDestinationNickname();
             if (name == null)
-                name = (out != null ? out.getDestinationNickname() : null);
+                name = out.getDestinationNickname();
             if (name == null)
                 name = dest.calculateHash().toBase64().substring(0,6);
         
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 a7a8da680d..2132a150d4 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
@@ -76,7 +76,7 @@ public class Connection {
     private long _lifetimeDupMessageReceived;
     
     public static final long MAX_RESEND_DELAY = 60*1000;
-    public static final long MIN_RESEND_DELAY = 30*1000;
+    public static final long MIN_RESEND_DELAY = 20*1000;
 
     /** wait up to 5 minutes after disconnection so we can ack/close packets */
     public static int DISCONNECT_TIMEOUT = 5*60*1000;
@@ -181,6 +181,11 @@ public class Connection {
             }
         }
     }
+    void windowAdjusted() {
+        synchronized (_outboundPackets) {
+            _outboundPackets.notifyAll();
+        }
+    }
     
     void ackImmediately() {
         _receiver.send(null, 0, 0);
@@ -866,6 +871,7 @@ public class Connection {
                                       + ") for " + Connection.this.toString());
 
                         getOptions().setWindowSize(newWindowSize);
+                        windowAdjusted();
                     }
                 }
                 
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
index 92879b2684..5757e39878 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionOptions.java
@@ -160,6 +160,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
     public void setWindowSize(int numMsgs) { 
         if (numMsgs > _maxWindowSize)
             numMsgs = _maxWindowSize;
+        else if (numMsgs <= 0)
+            numMsgs = 1;
         _windowSize = numMsgs; 
     }
     
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java
index 1f63786c30..b95a6f643b 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java
@@ -224,6 +224,9 @@ public class ConnectionPacketHandler {
                     newWindowSize += 1;
                 }
             }
+            
+            if (newWindowSize <= 0)
+                newWindowSize = 1;
                     
             if (_log.shouldLog(Log.DEBUG))
                 _log.debug("New window size " + newWindowSize + "/" + oldWindow + " congestionSeenAt: "
@@ -233,6 +236,7 @@ public class ConnectionPacketHandler {
             con.setCongestionWindowEnd(newWindowSize + lowest);
         }
         
+        con.windowAdjusted();
         return congested;
     }
     
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnectedBulk.java b/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnectedBulk.java
index a7616da5f6..fcbaa5b2c9 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnectedBulk.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnectedBulk.java
@@ -38,7 +38,7 @@ class SchedulerConnectedBulk extends SchedulerImpl {
     
     public boolean accept(Connection con) {
         boolean ok = (con != null) && 
-                     (con.getAckedPackets() > 0) &&
+                     (con.getHighestAckedThrough() >= 0) &&
                      (con.getOptions().getProfile() == ConnectionOptions.PROFILE_BULK) &&
                      (!con.getResetReceived()) &&
                      ( (con.getCloseSentOn() <= 0) || (con.getCloseReceivedOn() <= 0) );
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnecting.java b/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnecting.java
index f5f6b50ac2..5e4930116d 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnecting.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/SchedulerConnecting.java
@@ -39,7 +39,7 @@ class SchedulerConnecting extends SchedulerImpl {
         boolean notYetConnected = (con.getIsConnected()) &&
                                   //(con.getSendStreamId() == null) && // not null on recv
                                   (con.getLastSendId() >= 0) &&
-                                  (con.getAckedPackets() <= 0) && 
+                                  (con.getHighestAckedThrough() < 0) && 
                                   (!con.getResetReceived());
         return notYetConnected;
     }
diff --git a/router/java/src/net/i2p/router/RouterThrottleImpl.java b/router/java/src/net/i2p/router/RouterThrottleImpl.java
index 5519b4340d..1aad451621 100644
--- a/router/java/src/net/i2p/router/RouterThrottleImpl.java
+++ b/router/java/src/net/i2p/router/RouterThrottleImpl.java
@@ -24,11 +24,11 @@ class RouterThrottleImpl implements RouterThrottle {
     private static int JOB_LAG_LIMIT = 2000;
     /**
      * Arbitrary hard limit - if we throttle our network connection this many
-     * times in the previous 10-20 minute period, don't accept requests to 
+     * times in the previous 2 minute period, don't accept requests to 
      * participate in tunnels.
      *
      */
-    private static int THROTTLE_EVENT_LIMIT = 300;
+    private static int THROTTLE_EVENT_LIMIT = 30;
     
     private static final String PROP_MAX_TUNNELS = "router.maxParticipatingTunnels";
     private static final String PROP_DEFAULT_KBPS_THROTTLE = "router.defaultKBpsThrottle";
@@ -81,7 +81,7 @@ class RouterThrottleImpl implements RouterThrottle {
         RateStat rs = _context.statManager().getRate("router.throttleNetworkCause");
         Rate r = null;
         if (rs != null)
-            r = rs.getRate(10*60*1000);
+            r = rs.getRate(60*1000);
         long throttleEvents = (r != null ? r.getCurrentEventCount() + r.getLastEventCount() : 0);
         if (throttleEvents > THROTTLE_EVENT_LIMIT) {
             if (_log.shouldLog(Log.DEBUG))
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 20c8aea291..9f1ef182b8 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
  *
  */
 public class RouterVersion {
-    public final static String ID = "$Revision: 1.143 $ $Date: 2005/02/19 18:20:57 $";
+    public final static String ID = "$Revision: 1.144 $ $Date: 2005/02/20 04:12:46 $";
     public final static String VERSION = "0.5";
-    public final static long BUILD = 2;
+    public final static long BUILD = 3;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION);
         System.out.println("Router ID: " + RouterVersion.ID);
diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java
index 474c9ccc43..0f4e7ba8b4 100644
--- a/router/java/src/net/i2p/router/client/ClientManager.java
+++ b/router/java/src/net/i2p/router/client/ClientManager.java
@@ -223,7 +223,7 @@ public class ClientManager {
         }
     }
 
-    private static final int REQUEST_LEASESET_TIMEOUT = 20*1000;
+    private static final int REQUEST_LEASESET_TIMEOUT = 120*1000;
     public void requestLeaseSet(Hash dest, LeaseSet ls) {
         ClientConnectionRunner runner = getRunner(dest);
         if (runner != null)  {
diff --git a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
index 618ff80db0..fca120e493 100644
--- a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
+++ b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
@@ -41,6 +41,9 @@ class RequestLeaseSetJob extends JobImpl {
         _expiration = expiration;
         _onCreate = onCreate;
         _onFail = onFail;
+        ctx.statManager().createRateStat("client.requestLeaseSetSuccess", "How frequently the router requests successfully a new leaseSet?", "ClientMessages", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
+        ctx.statManager().createRateStat("client.requestLeaseSetTimeout", "How frequently the router requests a new leaseSet but gets no reply?", "ClientMessages", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
+        ctx.statManager().createRateStat("client.requestLeaseSetDropped", "How frequently the router requests a new leaseSet but the client drops?", "ClientMessages", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
     }
     
     public String getName() { return "Request Lease Set"; }
@@ -80,6 +83,7 @@ class RequestLeaseSetJob extends JobImpl {
             getContext().jobQueue().addJob(new CheckLeaseRequestStatus(getContext(), state));
             return;
         } catch (I2CPMessageException ime) {
+            getContext().statManager().addRateData("client.requestLeaseSetDropped", 1, 0);
             _log.error("Error sending I2CP message requesting the lease set", ime);
             state.setIsSuccessful(false);
             _runner.setLeaseRequest(null);
@@ -107,9 +111,13 @@ class RequestLeaseSetJob extends JobImpl {
             if (_runner.isDead()) return;
             if (_req.getIsSuccessful()) {
                 // we didn't fail
+                RequestLeaseSetJob.CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetSuccess", 1, 0);
                 return;
             } else {
-                _log.error("Failed to receive a leaseSet in the time allotted (" + new Date(_req.getExpiration()) + ")");
+                RequestLeaseSetJob.CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetTimeout", 1, 0);
+                if (_log.shouldLog(Log.CRIT))
+                    _log.log(Log.CRIT, "Failed to receive a leaseSet in the time allotted (" + new Date(_req.getExpiration()) + ") for " 
+                             + _runner.getConfig().getDestination().calculateHash().toBase64());
                 _runner.disconnectClient("Took too long to request leaseSet");
                 if (_req.getOnFailed() != null)
                     RequestLeaseSetJob.this.getContext().jobQueue().addJob(_req.getOnFailed());
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java
index 1618b4cade..af5027326d 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/SearchUpdateReplyFoundJob.java
@@ -37,14 +37,15 @@ class SearchUpdateReplyFoundJob extends JobImpl implements ReplyJob {
     
     public String getName() { return "Update Reply Found for Kademlia Search"; }
     public void runJob() {
+        I2NPMessage message = _message;
         if (_log.shouldLog(Log.INFO))
             _log.info(getJobId() + ": Reply from " + _peer.toBase64() 
-                      + " with message " + _message.getClass().getName());
+                      + " with message " + message.getClass().getName());
         
-        if (_message instanceof DatabaseStoreMessage) {
+        if (message instanceof DatabaseStoreMessage) {
             long timeToReply = _state.dataFound(_peer);
             
-            DatabaseStoreMessage msg = (DatabaseStoreMessage)_message;
+            DatabaseStoreMessage msg = (DatabaseStoreMessage)message;
             if (msg.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET) {
                 try {
                     _facade.store(msg.getKey(), msg.getLeaseSet());
@@ -71,11 +72,11 @@ class SearchUpdateReplyFoundJob extends JobImpl implements ReplyJob {
                 if (_log.shouldLog(Log.ERROR))
                     _log.error(getJobId() + ": Unknown db store type?!@ " + msg.getValueType());
             }
-        } else if (_message instanceof DatabaseSearchReplyMessage) {
-            _job.replyFound((DatabaseSearchReplyMessage)_message, _peer);
+        } else if (message instanceof DatabaseSearchReplyMessage) {
+            _job.replyFound((DatabaseSearchReplyMessage)message, _peer);
         } else {
             if (_log.shouldLog(Log.ERROR))
-                _log.error(getJobId() + ": WTF, reply job matched a strange message: " + _message);
+                _log.error(getJobId() + ": WTF, reply job matched a strange message: " + message);
             return;
         }
         
diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
index b20867e80c..1b5a0cb926 100644
--- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
+++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java
@@ -363,8 +363,8 @@ public class FragmentHandler {
             }
             if (removed && !_msg.getReleased()) {
                 noteFailure(_msg.getMessageId());
-                if (_log.shouldLog(Log.ERROR))
-                    _log.error("Dropped failed fragmented message: " + _msg);
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("Dropped failed fragmented message: " + _msg);
                 _context.statManager().addRateData("tunnel.fragmentedDropped", _msg.getFragmentCount(), _msg.getLifetime());
                 _msg.failed();
             } else {
diff --git a/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java
index 19ebc5ec3c..145496f704 100644
--- a/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java
+++ b/router/java/src/net/i2p/router/tunnel/TunnelParticipant.java
@@ -27,6 +27,7 @@ public class TunnelParticipant {
     private InboundEndpointProcessor _inboundEndpointProcessor;
     private InboundMessageDistributor _inboundDistributor;
     private FragmentHandler _handler;
+    private RouterInfo _nextHopCache;
 
     public TunnelParticipant(RouterContext ctx, HopConfig config, HopProcessor processor) {
         this(ctx, config, processor, null);
@@ -44,6 +45,10 @@ public class TunnelParticipant {
         _inboundEndpointProcessor = inEndProc;
         if (inEndProc != null)
             _inboundDistributor = new InboundMessageDistributor(ctx, inEndProc.getDestination());
+
+        if ( (_config != null) && (_config.getSendTo() != null) ) {
+            _nextHopCache = _context.netDb().lookupRouterInfoLocally(_config.getSendTo());
+        }
     }
     
     public void dispatch(TunnelDataMessage msg, Hash recvFrom) {
@@ -62,7 +67,9 @@ public class TunnelParticipant {
         
         if ( (_config != null) && (_config.getSendTo() != null) ) {
             _config.incrementProcessedMessages();
-            RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo());
+            RouterInfo ri = _nextHopCache;
+            if (ri == null)
+                ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo());
             if (ri != null) {
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("Send off to nextHop directly (" + _config.getSendTo().toBase64().substring(0,4) 
@@ -115,6 +122,7 @@ public class TunnelParticipant {
         public void runJob() {
             RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo());
             if (ri != null) {
+                _nextHopCache = ri;
                 send(_config, _msg, ri);
             } else {
                 if (_log.shouldLog(Log.ERROR))
@@ -134,6 +142,7 @@ public class TunnelParticipant {
         public void runJob() {
             RouterInfo ri = _context.netDb().lookupRouterInfoLocally(_config.getSendTo());
             if (ri != null) {
+                _nextHopCache = ri;
                 if (_log.shouldLog(Log.ERROR))
                     _log.error("Lookup the nextHop (" + _config.getSendTo().toBase64().substring(0,4) 
                               + " failed, but we found it!!  where do we go for " + _config + "?  msg dropped: " + _msg);
diff --git a/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java b/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java
index a0c3c00dde..902ad9235a 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java
@@ -20,7 +20,7 @@ class RebuildJob extends JobImpl {
         _cfg = cfg;
         _buildToken = buildToken;
         long rebuildOn = cfg.getExpiration() - pool.getSettings().getRebuildPeriod();
-        rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod());
+        rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod()*2);
         getTiming().setStartAfter(rebuildOn);
     }
     public String getName() { return "Rebuild tunnel"; }
diff --git a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
index 5a28b8301b..5275dedd3e 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
@@ -43,11 +43,12 @@ public class RequestTunnelJob extends JobImpl {
     private TunnelCreatorConfig _config;
     private long _lastSendTime;
     private boolean _isFake;
+    private boolean _isExploratory;
     
     static final int HOP_REQUEST_TIMEOUT = 30*1000;
     private static final int LOOKUP_TIMEOUT = 10*1000;
     
-    public RequestTunnelJob(RouterContext ctx, TunnelCreatorConfig cfg, Job onCreated, Job onFailed, int hop, boolean isFake) {
+    public RequestTunnelJob(RouterContext ctx, TunnelCreatorConfig cfg, Job onCreated, Job onFailed, int hop, boolean isFake, boolean isExploratory) {
         super(ctx);
         _log = ctx.logManager().getLog(RequestTunnelJob.class);
         _config = cfg;
@@ -58,13 +59,16 @@ public class RequestTunnelJob extends JobImpl {
         _lookups = 0;
         _lastSendTime = 0;
         _isFake = isFake;
+        _isExploratory = isExploratory;
         
         ctx.statManager().createRateStat("tunnel.receiveRejectionProbabalistic", "How often we are rejected probabalistically?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
         ctx.statManager().createRateStat("tunnel.receiveRejectionTransient", "How often we are rejected due to transient overload?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
         ctx.statManager().createRateStat("tunnel.receiveRejectionBandwidth", "How often we are rejected due to bandwidth overload?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
         ctx.statManager().createRateStat("tunnel.receiveRejectionCritical", "How often we are rejected due to critical failure?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
-        ctx.statManager().createRateStat("tunnel.buildFailure", "How often we fail to build a tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
-        ctx.statManager().createRateStat("tunnel.buildSuccess", "How often we succeed building a tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildFailure", "How often we fail to build a non-exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratoryFailure", "How often we fail to build an exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildSuccess", "How often we succeed building a non-exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratorySuccess", "How often we succeed building an exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
 
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Requesting hop " + hop + " in " + cfg);
@@ -108,7 +112,7 @@ public class RequestTunnelJob extends JobImpl {
                                + _currentConfig.getReceiveTunnel() + ": " + _config);
                 // inbound tunnel with more than just ourselves
                 RequestTunnelJob req = new RequestTunnelJob(getContext(), _config, _onCreated, 
-                                                            _onFailed, _currentHop - 1, _isFake);
+                                                            _onFailed, _currentHop - 1, _isFake, _isExploratory);
                 if (_isFake)
                     req.runJob();
                 else
@@ -257,19 +261,25 @@ public class RequestTunnelJob extends JobImpl {
             _log.info("tunnel building failed: " + _config + " at hop " + _currentHop);
         if (_onFailed != null)
             getContext().jobQueue().addJob(_onFailed);
-        getContext().statManager().addRateData("tunnel.buildFailure", 1, 0);
+        if (_isExploratory)
+            getContext().statManager().addRateData("tunnel.buildExploratoryFailure", 1, 0);
+        else
+            getContext().statManager().addRateData("tunnel.buildFailure", 1, 0);
     }
     
     private void peerSuccess() {
         getContext().profileManager().tunnelJoined(_currentPeer.getIdentity().calculateHash(), 
                                                    getContext().clock().now() - _lastSendTime);
         if (_currentHop > 0) {
-            RequestTunnelJob j = new RequestTunnelJob(getContext(), _config, _onCreated, _onFailed, _currentHop - 1, _isFake);
+            RequestTunnelJob j = new RequestTunnelJob(getContext(), _config, _onCreated, _onFailed, _currentHop - 1, _isFake, _isExploratory);
             getContext().jobQueue().addJob(j);
         } else {
             if (_onCreated != null)
                 getContext().jobQueue().addJob(_onCreated);
-            getContext().statManager().addRateData("tunnel.buildSuccess", 1, 0);
+            if (_isExploratory)
+                getContext().statManager().addRateData("tunnel.buildExploratorySuccess", 1, 0);
+            else
+                getContext().statManager().addRateData("tunnel.buildSuccess", 1, 0);
         }
     }
     
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java
index ceeac0e881..5a2d4366f4 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java
@@ -40,6 +40,8 @@ class TestJob extends JobImpl {
         getTiming().setStartAfter(getDelay() + ctx.clock().now());
         ctx.statManager().createRateStat("tunnel.testFailedTime", "How long did the failure take (max of 60s for full timeout)?", "Tunnels", 
                                          new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.testExploratoryFailedTime", "How long did the failure of an exploratory tunnel take (max of 60s for full timeout)?", "Tunnels", 
+                                         new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
         ctx.statManager().createRateStat("tunnel.testSuccessLength", "How long were the tunnels that passed the test?", "Tunnels", 
                                          new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
         ctx.statManager().createRateStat("tunnel.testSuccessTime", "How long did tunnel testing take?", "Tunnels", 
@@ -132,7 +134,10 @@ class TestJob extends JobImpl {
     }
     
     private void testFailed(long timeToFail) {
-        getContext().statManager().addRateData("tunnel.testFailedTime", timeToFail, timeToFail);
+        if (_pool.getSettings().isExploratory())
+            getContext().statManager().addRateData("tunnel.testExploratoryFailedTime", timeToFail, timeToFail);
+        else
+            getContext().statManager().addRateData("tunnel.testFailedTime", timeToFail, timeToFail);
         _cfg.tunnelFailed();
         if (_log.shouldLog(Log.WARN))
             _log.warn("Tunnel test failed in " + timeToFail + "ms: " + _cfg);
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java
index 1c123cf095..22ca20419d 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java
@@ -49,7 +49,7 @@ public class TunnelBuilder {
         // queue up a job to request the endpoint to join the tunnel, which then
         // requeues up another job for earlier hops, etc, until it reaches the 
         // gateway.  after the gateway is confirmed, onCreated is fired
-        RequestTunnelJob req = new RequestTunnelJob(ctx, cfg, onCreated, onFailed, cfg.getLength()-1, fake);
+        RequestTunnelJob req = new RequestTunnelJob(ctx, cfg, onCreated, onFailed, cfg.getLength()-1, fake, pool.getSettings().isExploratory());
         if (fake) // lets get it done inline, as we /need/ it asap
             req.runJob();
         else
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
index 8fa8cf248c..b519651566 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
@@ -137,6 +137,33 @@ public class TunnelPool {
             if (_log.shouldLog(Log.INFO))
                 _log.info(toString() + ": keepBuilding does NOT want building to continue (want " 
                           + wanted + ", have " + remaining);
+        } else {
+            boolean needed = true;
+            int valid = 0;
+            synchronized (_tunnels) {
+                if (_tunnels.size() > wanted) {
+                    for (int i = 0; i < _tunnels.size(); i++) {
+                        TunnelInfo info = (TunnelInfo)_tunnels.get(i);
+                        if (info.getExpiration() > _context.clock().now() + 3*_settings.getRebuildPeriod()) {
+                            valid++;
+                            if (valid >= wanted*2)
+                                break;
+                        }
+                    }
+                    if (valid >= wanted*2)
+                        needed = false;
+                }
+            }
+
+            if (!needed) {
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn(toString() + ": keepBuilding wants building to continue, but not "
+                              + " with the current object... # tunnels = " + valid + ", wanted = " + wanted);
+                synchronized (_tokens) {
+                    _tokens.remove(token);
+                }
+                return false;
+            }
         }
         return rv;
     }
@@ -279,7 +306,7 @@ public class TunnelPool {
 
     public void tunnelFailed(PooledTunnelCreatorConfig cfg) {
         if (_log.shouldLog(Log.WARN))
-            _log.warn(toString() + ": Tunnel failed: " + cfg, new Exception("failure cause"));
+            _log.warn(toString() + ": Tunnel failed: " + cfg);
         int remaining = 0;
         LeaseSet ls = null;
         synchronized (_tunnels) {
@@ -337,7 +364,7 @@ public class TunnelPool {
                 int valid = 0;
                 for (int i = 0; i < _tunnels.size(); i++) {
                     TunnelInfo info = (TunnelInfo)_tunnels.get(i);
-                    if (info.getExpiration() > _context.clock().now()) {
+                    if (info.getExpiration() > _context.clock().now() + 3*_settings.getRebuildPeriod()) {
                         valid++;
                         if (valid >= quantity)
                             break;
-- 
GitLab