From ab4f3008cb78a7ef0ea6b25b04e9b6eeb83f8443 Mon Sep 17 00:00:00 2001
From: jrandom <jrandom>
Date: Fri, 9 Dec 2005 08:05:44 +0000
Subject: [PATCH] 2005-12-09  zzz     * Create different strategies for
 exploratory tunnels (which are difficult       to create) and client tunnels
 (which are much easier)     * Gradually increase number of parallel build
 attempts as tunnel expiry       nears.     * Temporarily shorten attempted
 build tunnel length if builds using       configured tunnel length are
 unsuccessful     * React more aggressively to tunnel failure than routine
 tunnel       replacement     * Make tunnel creation times randomized - there
 is existing code to       randomize the tunnels but it isn't effective due to
 the tunnel creation       strategy. Currently, most tunnels get built all at
 once, at about 2 1/2       to 3 minutes before expiration. The patch fixes
 this by fixing the       randomization, and by changing the overlap time
 (with old tunnels) to a       range of 2 to 4 minutes.     * Reduce number of
 excess tunnels. Lots of excess tunnels get created due       to overlapping
 calls. Just about anything generated a call which could       build many
 tunnels all at once, even if tunnel building was already in       process.   
  * Miscellaneous router console enhancements

---
 .../i2p/router/web/ConfigLoggingHelper.java   |   2 +
 .../i2p/router/web/ConfigTunnelsHandler.java  |   2 +-
 .../net/i2p/router/web/JobQueueHelper.java    |  46 +++++++
 .../src/net/i2p/router/web/SummaryHelper.java |  47 +++++--
 apps/routerconsole/jsp/configtunnels.jsp      |   3 +
 apps/routerconsole/jsp/jobs.jsp               |  21 ++++
 apps/routerconsole/jsp/nav.jsp                |   2 +
 apps/routerconsole/jsp/summary.jsp            |   6 +-
 history.txt                                   |  23 +++-
 .../src/net/i2p/router/RouterVersion.java     |   4 +-
 .../src/net/i2p/router/RouterWatchdog.java    |   4 +-
 .../net/i2p/router/TunnelManagerFacade.java   |   6 +
 .../net/i2p/router/TunnelPoolSettings.java    |   6 +
 .../net/i2p/router/tunnel/pool/ExpireJob.java |  17 ++-
 .../i2p/router/tunnel/pool/RebuildJob.java    |  45 ++++++-
 .../router/tunnel/pool/RequestTunnelJob.java  |  32 ++++-
 .../i2p/router/tunnel/pool/TunnelBuilder.java |   2 +-
 .../tunnel/pool/TunnelPeerSelector.java       |   5 +-
 .../i2p/router/tunnel/pool/TunnelPool.java    | 119 +++++++++++++-----
 .../router/tunnel/pool/TunnelPoolManager.java |  44 ++++++-
 20 files changed, 365 insertions(+), 71 deletions(-)
 create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java
 create mode 100644 apps/routerconsole/jsp/jobs.jsp

diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java
index 4ca03b1e5d..0f2188bffd 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java
@@ -59,6 +59,8 @@ public class ConfigLoggingHelper {
             buf.append(prefix).append('=').append(level).append('\n');
         }
         buf.append("</textarea><br />\n");
+        buf.append("<i>Add additional logging statements above. Example: net.i2p.router.tunnel=WARN</i><br>");
+        buf.append("<i>Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN</i><br>");
         buf.append("<i>Valid levels are DEBUG, INFO, WARN, ERROR, CRIT</i>\n");
         return buf.toString();
     }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java
index 73ebcf87ea..c64fbc4e9f 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java
@@ -135,7 +135,7 @@ public class ConfigTunnelsHandler extends FormHandler {
         if (saveRequired) {
             boolean saved = _context.router().saveConfig();
             if (saved) 
-                addFormNotice("Configuration saved successfully");
+                addFormNotice("Exploratory tunnel configuration saved successfully");
             else
                 addFormNotice("Error saving the configuration (applied but not saved) - please see the error logs");
         }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java
new file mode 100644
index 0000000000..a56cce19af
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java
@@ -0,0 +1,46 @@
+package net.i2p.router.web;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import net.i2p.router.RouterContext;
+
+public class JobQueueHelper {
+    private RouterContext _context;
+    private Writer _out;
+    /**
+     * Configure this bean to query a particular router context
+     *
+     * @param contextId begging few characters of the routerHash, or null to pick
+     *                  the first one we come across.
+     */
+    public void setContextId(String contextId) {
+        try {
+            _context = ContextHelper.getContext(contextId);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+    
+    public JobQueueHelper() {}
+    
+    public void setWriter(Writer writer) { _out = writer; }
+    
+    public String getJobQueueSummary() {
+        try {
+            if (_out != null) {
+                _context.jobQueue().renderStatusHTML(_out);
+                return "";
+            } else {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
+                _context.jobQueue().renderStatusHTML(new OutputStreamWriter(baos));
+                return new String(baos.toByteArray());
+            }
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+            return "";
+        }
+    }
+}
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
index 1c26f5bd07..bcaee116f0 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -83,14 +83,23 @@ public class SummaryHelper {
         
         long ms = _context.clock().getOffset();
         
-        if (ms < 60 * 1000) {
-            return now + " (" + (ms / 1000) + "s)";
-        } else if (ms < 60 * 60 * 1000) {
-            return now + " (" + (ms / (60 * 1000)) + "m)";
-        } else if (ms < 24 * 60 * 60 * 1000) {
-            return now + " (" + (ms / (60 * 60 * 1000)) + "h)";
+        long diff = ms;
+        if (diff < 0)
+            diff = 0 - diff;
+        if (diff == 0) {
+            return now + " (no skew)";
+        } else if (diff < 1000) {
+            return now + " (" + ms + "ms skew)";
+        } else if (diff < 5 * 1000) {
+            return now + " (" + (ms / 1000) + "s skew)";
+        } else if (diff < 60 * 1000) {
+            return now + " <b>(" + (ms / 1000) + "s skew)</b>";
+        } else if (diff < 60 * 60 * 1000) {
+            return now + " <b>(" + (ms / (60 * 1000)) + "m skew)</b>";
+        } else if (diff < 24 * 60 * 60 * 1000) {
+            return now + " <b>(" + (ms / (60 * 60 * 1000)) + "h skew)</b>";
         } else {
-            return now + " (" + (ms / (24 * 60 * 60 * 1000)) + "d)";
+            return now + " <b>(" + (ms / (24 * 60 * 60 * 1000)) + "d skew)</b>";
         }
     }
     
@@ -408,6 +417,28 @@ public class SummaryHelper {
             return _context.tunnelManager().getOutboundTunnelCount();
     }
     
+    /**
+     * How many inbound client tunnels we have.
+     *
+     */
+    public int getInboundClientTunnels() { 
+        if (_context == null) 
+            return 0;
+        else
+            return _context.tunnelManager().getInboundClientTunnelCount();
+    }
+    
+    /**
+     * How many active outbound client tunnels we have.
+     *
+     */
+    public int getOutboundClientTunnels() { 
+        if (_context == null) 
+            return 0;
+        else
+            return _context.tunnelManager().getOutboundClientTunnelCount();
+    }
+    
     /**
      * How many tunnels we are participating in.
      *
@@ -459,4 +490,4 @@ public class SummaryHelper {
     public boolean updateAvailable() { 
         return NewsFetcher.getInstance(_context).updateAvailable();
     }
-}
\ No newline at end of file
+}
diff --git a/apps/routerconsole/jsp/configtunnels.jsp b/apps/routerconsole/jsp/configtunnels.jsp
index 84ecf43418..0b6fcb714b 100644
--- a/apps/routerconsole/jsp/configtunnels.jsp
+++ b/apps/routerconsole/jsp/configtunnels.jsp
@@ -33,6 +33,9 @@
  <input type="hidden" name="action" value="blah" />
  <jsp:getProperty name="tunnelshelper" property="form" />
  <hr />
+ <i>Note - Exploratory tunnel setting changes are stored in the router.config file.</i></br>
+ <i>Client tunnel changes are temporary and are not saved.</i><br>
+ <i>To make permanent client tunnel changes see the </i><a href="i2ptunnel/index.jsp">i2ptunnel page</a>.<br>
  <input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
  </form>
 </div>
diff --git a/apps/routerconsole/jsp/jobs.jsp b/apps/routerconsole/jsp/jobs.jsp
new file mode 100644
index 0000000000..56701af6b4
--- /dev/null
+++ b/apps/routerconsole/jsp/jobs.jsp
@@ -0,0 +1,21 @@
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html><head>
+<title>I2P Router Console - job queue</title>
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head><body>
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+
+<div class="main" id="main">
+ <jsp:useBean class="net.i2p.router.web.JobQueueHelper" id="jobQueueHelper" scope="request" />
+ <jsp:setProperty name="jobQueueHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
+ <jsp:setProperty name="jobQueueHelper" property="writer" value="<%=out%>" />
+ <jsp:getProperty name="jobQueueHelper" property="jobQueueSummary" />
+</div>
+
+</body>
+</html>
diff --git a/apps/routerconsole/jsp/nav.jsp b/apps/routerconsole/jsp/nav.jsp
index 414b7c7f25..57c8264667 100644
--- a/apps/routerconsole/jsp/nav.jsp
+++ b/apps/routerconsole/jsp/nav.jsp
@@ -18,11 +18,13 @@
  <a href="susimail/susimail">Susimail</a> |
  <a href="susidns/index.jsp">SusiDNS</a> |
  <a href="syndie/">Syndie</a> |
+ <a href="http://localhost:7658/">My Eepsite</a> <br>
  <a href="i2ptunnel/index.jsp">I2PTunnel</a> |
  <a href="tunnels.jsp">Tunnels</a> |
  <a href="profiles.jsp">Profiles</a> |
  <a href="netdb.jsp">NetDB</a> |
  <a href="logs.jsp">Logs</a> |
+ <a href="jobs.jsp">Jobs</a> |
  <a href="oldstats.jsp">Stats</a> |
  <a href="oldconsole.jsp">Internals</a>
  <jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" />
diff --git a/apps/routerconsole/jsp/summary.jsp b/apps/routerconsole/jsp/summary.jsp
index ad45e9328c..9d246ddb68 100644
--- a/apps/routerconsole/jsp/summary.jsp
+++ b/apps/routerconsole/jsp/summary.jsp
@@ -72,9 +72,9 @@
  
  <jsp:getProperty name="helper" property="destinations" />
  
- <u><b>Tunnels</b></u><br />
- <b>Inbound:</b> <jsp:getProperty name="helper" property="inboundTunnels" /><br />
- <b>Outbound:</b> <jsp:getProperty name="helper" property="outboundTunnels" /><br />
+ <u><b>Tunnels in/out</b></u><br />
+ <b>Exploratory:</b> <jsp:getProperty name="helper" property="inboundTunnels" />/<jsp:getProperty name="helper" property="outboundTunnels" /><br />
+ <b>Client:</b> <jsp:getProperty name="helper" property="inboundClientTunnels" />/<jsp:getProperty name="helper" property="outboundClientTunnels" /><br />
  <b>Participating:</b> <jsp:getProperty name="helper" property="participatingTunnels" /><br />
  <hr />
  
diff --git a/history.txt b/history.txt
index bc98dcadbf..553e0093ef 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,25 @@
-$Id: history.txt,v 1.350 2005/12/07 19:50:35 jrandom Exp $
+$Id: history.txt,v 1.351 2005/12/08 15:53:43 jrandom Exp $
+
+2005-12-09  zzz
+    * Create different strategies for exploratory tunnels (which are difficult
+      to create) and client tunnels (which are much easier)
+    * Gradually increase number of parallel build attempts as tunnel expiry
+      nears.
+    * Temporarily shorten attempted build tunnel length if builds using
+      configured tunnel length are unsuccessful
+    * React more aggressively to tunnel failure than routine tunnel
+      replacement
+    * Make tunnel creation times randomized - there is existing code to
+      randomize the tunnels but it isn't effective due to the tunnel creation 
+      strategy. Currently, most tunnels get built all at once, at about 2 1/2
+      to 3 minutes before expiration. The patch fixes this by fixing the 
+      randomization, and by changing the overlap time (with old tunnels) to a
+      range of 2 to 4 minutes.
+    * Reduce number of excess tunnels. Lots of excess tunnels get created due
+      to overlapping calls. Just about anything generated a call which could
+      build many tunnels all at once, even if tunnel building was already in
+      process.
+    * Miscellaneous router console enhancements
 
 2005-12-08  jrandom
     * Minor bugfix in SSU for dealing with corrupt packets
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 12b75cd616..dff197ec62 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.307 $ $Date: 2005/12/01 12:16:54 $";
+    public final static String ID = "$Revision: 1.308 $ $Date: 2005/12/08 15:53:41 $";
     public final static String VERSION = "0.6.1.7";
-    public final static long BUILD = 1;
+    public final static long BUILD = 2;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
         System.out.println("Router ID: " + RouterVersion.ID);
diff --git a/router/java/src/net/i2p/router/RouterWatchdog.java b/router/java/src/net/i2p/router/RouterWatchdog.java
index eafdd46d11..f8f78a7631 100644
--- a/router/java/src/net/i2p/router/RouterWatchdog.java
+++ b/router/java/src/net/i2p/router/RouterWatchdog.java
@@ -90,7 +90,9 @@ class RouterWatchdog implements Runnable {
     
     public void monitorRouter() {
         boolean ok = verifyJobQueueLiveliness();
-        ok = ok && verifyClientLiveliness();
+        // If we aren't connected to the network that's why there's nobody to talk to
+        int netErrors = (int) _context.statManager().getRate("udp.sendException").getRate(60*1000).getLastEventCount();
+        ok = ok && (verifyClientLiveliness() || netErrors >= 5);
         
         if (!ok) {
             dumpStatus();
diff --git a/router/java/src/net/i2p/router/TunnelManagerFacade.java b/router/java/src/net/i2p/router/TunnelManagerFacade.java
index 67a2c4033a..052af69b8a 100644
--- a/router/java/src/net/i2p/router/TunnelManagerFacade.java
+++ b/router/java/src/net/i2p/router/TunnelManagerFacade.java
@@ -48,6 +48,10 @@ public interface TunnelManagerFacade extends Service {
     public int getFreeTunnelCount();
     /** how many outbound tunnels do we have available? */
     public int getOutboundTunnelCount();
+    /** how many free inbound client tunnels do we have available? */
+    public int getInboundClientTunnelCount();
+    /** how many outbound client tunnels do we have available? */
+    public int getOutboundClientTunnelCount();
     
     /** When does the last tunnel we are participating in expire? */
     public long getLastParticipatingExpiration();
@@ -81,6 +85,8 @@ class DummyTunnelManagerFacade implements TunnelManagerFacade {
     public int getParticipatingCount() { return 0; }
     public int getFreeTunnelCount() { return 0; }
     public int getOutboundTunnelCount() { return 0; }
+    public int getInboundClientTunnelCount() { return 0; }
+    public int getOutboundClientTunnelCount() { return 0; }
     public long getLastParticipatingExpiration() { return -1; }
     public void buildTunnels(Destination client, ClientTunnelSettings settings) {}
     public TunnelPoolSettings getInboundSettings() { return null; }
diff --git a/router/java/src/net/i2p/router/TunnelPoolSettings.java b/router/java/src/net/i2p/router/TunnelPoolSettings.java
index 8df755a28c..aaeb5cec37 100644
--- a/router/java/src/net/i2p/router/TunnelPoolSettings.java
+++ b/router/java/src/net/i2p/router/TunnelPoolSettings.java
@@ -18,6 +18,7 @@ public class TunnelPoolSettings {
     private int _duration;
     private int _length;
     private int _lengthVariance;
+    private int _lengthOverride;
     private boolean _isInbound;
     private boolean _isExploratory;
     private boolean _allowZeroHop;
@@ -54,6 +55,7 @@ public class TunnelPoolSettings {
         _duration = DEFAULT_DURATION;
         _length = DEFAULT_LENGTH;
         _lengthVariance = DEFAULT_LENGTH_VARIANCE;
+        _lengthOverride = 0;
         _allowZeroHop = DEFAULT_ALLOW_ZERO_HOP;
         _isInbound = false;
         _isExploratory = false;
@@ -90,6 +92,10 @@ public class TunnelPoolSettings {
      */
     public int getLengthVariance() { return _lengthVariance; }
     public void setLengthVariance(int variance) { _lengthVariance = variance; }
+
+    /* Set to a nonzero value to override the length setting */
+    public int getLengthOverride() { return _lengthOverride; }
+    public void setLengthOverride(int variance) { _lengthOverride = variance; }
     
     /** is this an inbound tunnel? */
     public boolean isInbound() { return _isInbound; }
diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java
index 83ec39570c..93dff3b542 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java
@@ -25,11 +25,16 @@ class ExpireJob extends JobImpl {
                 return "Expire exploratory outbound tunnel";
             }
         } else {
-            if (_pool.getSettings().isInbound()) {
-                return "Expire client inbound tunnel";
-            } else {
-                return "Expire client outbound tunnel";
-            }
+            StringBuffer rv = new StringBuffer(32);
+            if (_pool.getSettings().isInbound())
+                rv.append("Expire inbound client tunnel for ");
+            else
+                rv.append("Expire outbound client tunnel for ");
+            if (_pool.getSettings().getDestinationNickname() != null)
+                rv.append(_pool.getSettings().getDestinationNickname());
+            else
+                rv.append(_pool.getSettings().getDestination().toBase64().substring(0,4));
+            return rv.toString();
         }
     }
     public void runJob() {
@@ -44,4 +49,4 @@ class ExpireJob extends JobImpl {
             getContext().tunnelDispatcher().remove(_cfg);
         }
     }
-}
\ No newline at end of file
+}
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 fb5cad7cb7..fd4a6eda3d 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java
@@ -7,7 +7,10 @@ import net.i2p.router.tunnel.TunnelCreatorConfig;
 /**
  * Build a new tunnel to replace the existing one before it expires.  This job
  * should be removed (or scheduled to run immediately) if the tunnel fails.
- *
+ * If an exploratory tunnel build at a random time between 3 1/2 and 4 minutes early;
+ * else if only one tunnel in pool build 4 minutes early;
+ * otherwise build at a random time between 2 and 4 minutes early.
+ * Five build attempts in parallel if an exploratory tunnel.
  */
 class RebuildJob extends JobImpl {
     private TunnelPool _pool;
@@ -17,12 +20,42 @@ class RebuildJob extends JobImpl {
         super(ctx);
         _pool = pool;
         _cfg = cfg;
-        long rebuildOn = cfg.getExpiration() - pool.getSettings().getRebuildPeriod();
-        rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod()*2);
+        long rebuildOn;
+        if (_pool.getSettings().isExploratory()) {
+            rebuildOn = cfg.getExpiration() - (((pool.getSettings().getRebuildPeriod() * 7) / 2));
+            rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod() / 2);
+        } else if ((pool.getSettings().getQuantity() + pool.getSettings().getBackupQuantity()) == 1) {
+            rebuildOn = cfg.getExpiration() - (pool.getSettings().getRebuildPeriod() * 4);
+        } else {
+            rebuildOn = cfg.getExpiration() - (pool.getSettings().getRebuildPeriod() * 2);
+            rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod() * 2);
+        }
         getTiming().setStartAfter(rebuildOn);
     }
-    public String getName() { return "Rebuild tunnel"; }
+    public String getName() {
+        if (_pool.getSettings().isExploratory()) {
+            if (_pool.getSettings().isInbound()) {
+                return "Rebuild exploratory inbound tunnel";
+            } else {
+                return "Rebuild exploratory outbound tunnel";
+            }
+        } else {
+            StringBuffer rv = new StringBuffer(32);
+            if (_pool.getSettings().isInbound())
+                rv.append("Rebuild inbound client tunnel for ");
+            else
+                rv.append("Rebuild outbound client tunnel for ");
+            if (_pool.getSettings().getDestinationNickname() != null)
+                rv.append(_pool.getSettings().getDestinationNickname());
+            else
+                rv.append(_pool.getSettings().getDestination().toBase64().substring(0,4));
+            return rv.toString();
+        }
+    }
     public void runJob() {
-        _pool.refreshBuilders();
+        if (_pool.getSettings().isExploratory())
+            _pool.refreshBuilders(4, 4);
+        else
+            _pool.refreshBuilders(1, 4);
     }
-}
\ No newline at end of file
+}
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 208d8031f5..44f075ebf2 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java
@@ -66,9 +66,15 @@ public class RequestTunnelJob extends JobImpl {
         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", "What hop was being requested when a nonexploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
-        ctx.statManager().createRateStat("tunnel.buildExploratoryFailure", "What hop was beiing requested when an exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratoryFailure", "What hop was being requested when an exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratoryFailure1Hop", "What hop was being requested when a 1 hop exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratoryFailure2Hop", "What hop was being requested when a 2 hop exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratoryFailure3Hop", "What hop was being requested when a 3 hop exploratory tunnel request failed?", "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 });
+        ctx.statManager().createRateStat("tunnel.buildExploratorySuccess1Hop", "How often we succeed building a 1 hop exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratorySuccess2Hop", "How often we succeed building a 2 hop exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
+        ctx.statManager().createRateStat("tunnel.buildExploratorySuccess3Hop", "How often we succeed building a 3 hop exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
         ctx.statManager().createRateStat("tunnel.buildPartialTime", "How long a non-exploratory request took to be accepted?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
         ctx.statManager().createRateStat("tunnel.buildExploratoryPartialTime", "How long an exploratory request took to be accepted?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
 
@@ -263,9 +269,16 @@ public class RequestTunnelJob extends JobImpl {
             _log.info("tunnel building failed: " + _config + " at hop " + _currentHop);
         if (_onFailed != null)
             getContext().jobQueue().addJob(_onFailed);
-        if (_isExploratory)
-            getContext().statManager().addRateData("tunnel.buildExploratoryFailure", _currentHop, _config.getLength());
-        else
+        if (_isExploratory) {
+            int i = _config.getLength();
+            getContext().statManager().addRateData("tunnel.buildExploratoryFailure", _currentHop, i);
+            if (i == 2)
+                getContext().statManager().addRateData("tunnel.buildExploratoryFailure1Hop", _currentHop, i);
+            else if (i == 3)
+                getContext().statManager().addRateData("tunnel.buildExploratoryFailure2Hop", _currentHop, i);
+            else if (i == 4)
+                getContext().statManager().addRateData("tunnel.buildExploratoryFailure3Hop", _currentHop, i);
+        } else
             getContext().statManager().addRateData("tunnel.buildFailure", _currentHop, _config.getLength());
     }
     
@@ -284,9 +297,16 @@ public class RequestTunnelJob extends JobImpl {
         } else {
             if (_onCreated != null)
                 getContext().jobQueue().addJob(_onCreated);
-            if (_isExploratory)
+            if (_isExploratory) {
+                int i = _config.getLength();
                 getContext().statManager().addRateData("tunnel.buildExploratorySuccess", 1, 0);
-            else
+                if (i == 2)
+                    getContext().statManager().addRateData("tunnel.buildExploratorySuccess1Hop", 1, 0);
+                else if (i == 3)
+                    getContext().statManager().addRateData("tunnel.buildExploratorySuccess2Hop", 1, 0);
+                else if (i == 4)
+                    getContext().statManager().addRateData("tunnel.buildExploratorySuccess3Hop", 1, 0);
+            } else
                 getContext().statManager().addRateData("tunnel.buildSuccess", 1, 0);
         }
     }
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 2655fa2040..1d0a5adbaa 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java
@@ -110,7 +110,7 @@ public class TunnelBuilder {
         public void runJob() {
             // yikes, nothing left, lets get some backup (if we're allowed)
             _pool.getManager().buildComplete();
-            _pool.refreshBuilders();
+            _pool.refreshBuilders(1, 4);
         }
     }
 }
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 980b1cc55b..0a07dc3eaf 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java
@@ -26,7 +26,10 @@ abstract class TunnelPeerSelector {
     
     protected int getLength(RouterContext ctx, TunnelPoolSettings settings) {
         int length = settings.getLength();
-        if (settings.getLengthVariance() != 0) {
+        int override = settings.getLengthOverride();
+        if (override != 0)
+            length = override;
+        else if (settings.getLengthVariance() != 0) {
             int skew = settings.getLengthVariance();
             if (skew > 0)
                 length += ctx.random().nextInt(skew+1);
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 16f3d06ea0..34ec54c269 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java
@@ -37,7 +37,7 @@ public class TunnelPool {
     private long _lastSelectionPeriod;
     
     /**
-     * Only 5 builds per minute per pool, even if we have failing tunnels,
+     * Only 10 builds per minute per pool, even if we have failing tunnels,
      * etc.  On overflow, the necessary additional tunnels are built by the
      * RefreshJob
      */
@@ -65,7 +65,7 @@ public class TunnelPool {
         _alive = true;
         _refreshJob.getTiming().setStartAfter(_context.clock().now() + 60*1000);
         _context.jobQueue().addJob(_refreshJob);
-        int added = refreshBuilders();
+        int added = refreshBuilders(0, 0);
         if (added <= 0) {
             // we just reconnected and didn't require any new tunnel builders.
             // however, we /do/ want a leaseSet, so build one
@@ -85,12 +85,17 @@ public class TunnelPool {
         _lastSelected = null;
     }
 
-    private int countUsableTunnels() {
+    /**
+     * Return number of tunnels expiring greater than
+     * timeFactor * RebuildPeriod from now
+     *
+     */
+    private int countUsableTunnels(int timeFactor) {
         int valid = 0;
         synchronized (_tunnels) {
             for (int i = 0; i < _tunnels.size(); i++) {
                 TunnelInfo info = (TunnelInfo)_tunnels.get(i);
-                if (info.getExpiration() > _context.clock().now() + 3*_settings.getRebuildPeriod())
+                if (info.getExpiration() > _context.clock().now() + (timeFactor * _settings.getRebuildPeriod()))
                     valid++;
             }
         }
@@ -99,21 +104,31 @@ public class TunnelPool {
     
     /**
      * Fire up as many buildTunnel tasks as necessary, returning how many
-     * were added
+     * were added.
+     * Build maxBuild tunnels (0 = unlimited), use timeFactor * RebuildPeriod.
+     * Fire off up to six extra jobs if an exploratory tunnel is
+     * requested by RebuildJob or tunnelFailed (maxBuild > 1).
+     * Throttle builds to a maximum per minute; reduce maximum if job lag is high,
+     * or if we have network errors which indicate we are disconnected from the network.
+     * Override pool length setting and build a 1-hop tunnel if time is short.
      *
      */
-    int refreshBuilders() {
+    int refreshBuilders(int maxBuild, int timeFactor) {
         if ( (_settings.getDestination() != null) && (!_context.clientManager().isLocal(_settings.getDestination())) )
             _alive = false;
         if (!_alive) return 0;
         // only start up new build tasks if we need more of 'em
-        int target = _settings.getQuantity() + _settings.getBackupQuantity();
-        int usableTunnels = countUsableTunnels();
+        int baseTarget = _settings.getQuantity() + _settings.getBackupQuantity();
+        int target = baseTarget;
+        int usableTunnels = countUsableTunnels(timeFactor);
+        if (_settings.isExploratory() && target > 0 && maxBuild > 1)
+            target+= 6;
         
-        if ( (target > usableTunnels) && (_log.shouldLog(Log.INFO)) )
-            _log.info(toString() + ": refreshing builders, previously had " + usableTunnels
+        if ( (target > usableTunnels)  )
+            if ( (target > usableTunnels) && (_log.shouldLog(Log.INFO)) )
+                _log.info(toString() + ": refreshing builders, previously had " + usableTunnels
                           + ", want a total of " + target + ", creating " 
-                          + (target-usableTunnels) + " new ones.");
+                          + (target-usableTunnels) + " new ones (" + maxBuild + " max).");
 
         if (target > usableTunnels) {
             long minute = _context.clock().now();
@@ -123,9 +138,34 @@ public class TunnelPool {
                 _buildsThisMinute = 0;
             }
             int build = (target - usableTunnels);
-            if (build > (MAX_BUILDS_PER_MINUTE - _buildsThisMinute))
-                build = (MAX_BUILDS_PER_MINUTE - _buildsThisMinute);
-            
+            if (maxBuild > 0 && build > maxBuild)
+                build = maxBuild;
+            int buildThrottle = MAX_BUILDS_PER_MINUTE;
+            int lag = (int) _context.statManager().getRate("jobQueue.jobLag").getRate(60*1000).getAverageValue();
+            int netErrors = (int) _context.statManager().getRate("udp.sendException").getRate(60*1000).getLastEventCount();
+            if (lag > 3 * 1000 || netErrors > 5) {
+                if (_log.shouldLog(Log.WARN))
+                    _log.warn("Throttling tunnel builds lag = " + lag + "; net errors = " + netErrors);
+                if (_settings.isExploratory())
+                    buildThrottle = 3;
+                else
+                    buildThrottle = 1;
+            } else if (lag > 1 * 1000) {
+                if (_settings.isExploratory())
+                    buildThrottle = 5;
+                else
+                    buildThrottle = 2;
+            }
+            if (build > (buildThrottle - _buildsThisMinute))
+                build = (buildThrottle - _buildsThisMinute);
+            if (build <= 0) return 0;
+
+            if ((_settings.isExploratory() && baseTarget > countUsableTunnels(1)) ||
+                ((!_settings.isExploratory()) && baseTarget > countUsableTunnels(0)))
+                    _settings.setLengthOverride(1);
+                else
+                    _settings.setLengthOverride(0);
+
             int wanted = build;
             build = _manager.allocateBuilds(build);
             
@@ -255,7 +295,7 @@ public class TunnelPool {
         if (_settings != null) {
             if (_log.shouldLog(Log.INFO))
                 _log.info(toString() + ": Settings updated on the pool: " + settings);
-            refreshBuilders(); // to start/stop new sequences, in case the quantities changed
+            refreshBuilders(1, 4); // to start/stop new sequences, in case the quantities changed
         }
     }
     public TunnelPeerSelector getSelector() { return _peerSelector; }
@@ -278,8 +318,6 @@ public class TunnelPool {
         
         if (ls != null)
             _context.clientManager().requestLeaseSet(_settings.getDestination(), ls);
-        
-        refreshBuilders();
     }
     
     public void removeTunnel(TunnelInfo info) {
@@ -319,7 +357,6 @@ public class TunnelPool {
             _manager.removeTunnels(_settings.getDestination());
             return;
         }
-        refreshBuilders();
     }
 
     public void tunnelFailed(PooledTunnelCreatorConfig cfg) {
@@ -343,15 +380,27 @@ public class TunnelPool {
         if (_settings.isInbound() && (_settings.getDestination() != null) ) {
             if (ls != null) {
                 _context.clientManager().requestLeaseSet(_settings.getDestination(), ls);
+                if (_settings.isExploratory())
+                    refreshBuilders(3, 4);
+                else
+                    refreshBuilders(1, 4);
             } else {
                 if (_log.shouldLog(Log.WARN))
                     _log.warn(toString() + ": unable to build a new leaseSet on failure (" + remaining 
                               + " remaining), request a new tunnel");
                 if (remaining < _settings.getBackupQuantity() + _settings.getQuantity())
-                    buildFallback();
+                    if (!buildFallback())
+                        if (_settings.isExploratory())
+                            refreshBuilders(3, 4);
+                        else
+                            refreshBuilders(1, 4);
             }
+        } else {
+            if (_settings.isExploratory())
+                refreshBuilders(3, 4);
+            else
+                refreshBuilders(1, 4);
         }
-        refreshBuilders();
     }
 
     void refreshLeaseSet() {
@@ -377,18 +426,24 @@ public class TunnelPool {
         }
     }
 
-    void buildFallback() {
+    /**
+     * Return true if a fallback tunnel is built
+     *
+     */
+    boolean buildFallback() {
         int quantity = _settings.getBackupQuantity() + _settings.getQuantity();
-        int usable = countUsableTunnels();
-        if (usable >= quantity) return;
+        int usable = countUsableTunnels(1);
+        if (usable >= quantity) return false;
 
-        if (_log.shouldLog(Log.INFO))
-            _log.info(toString() + ": building a fallback tunnel (usable: " + usable + " needed: " + quantity + ")");
-        if ( (usable == 0) && (_settings.getAllowZeroHop()) )
+        if ( (usable == 0) && (_settings.getAllowZeroHop()) ) {
+            if (_log.shouldLog(Log.INFO))
+                _log.info(toString() + ": building a fallback tunnel (usable: " + usable + " needed: " + quantity + ")");
             _builder.buildTunnel(_context, this, true);
+            return true;
+        }
         //else
         //    _builder.buildTunnel(_context, this);
-        refreshBuilders();
+        return false;
     }
     
     /**
@@ -476,12 +531,16 @@ public class TunnelPool {
         public RefreshJob(RouterContext ctx) {
             super(ctx);
         }
-        public String getName() { return "Refresh pool"; }
+        public String getName() { return "Refresh " + TunnelPool.this.toString(); }
         public void runJob() {
             if (!_alive) return;
-            int added = refreshBuilders();
+            int added;
+            if (_settings.isExploratory())
+                added = refreshBuilders(0, 2);
+            else
+                added = refreshBuilders(0, 1);
             if ( (added > 0) && (_log.shouldLog(Log.WARN)) )
-                _log.warn("Passive rebuilding a tunnel for " + TunnelPool.this.toString());
+                _log.warn("Additional parallel rebuilding of tunnel for " + TunnelPool.this.toString());
             requeue(30*1000);
         }
     }
diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
index 4367b437a0..a2a9de0195 100644
--- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
+++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java
@@ -154,6 +154,38 @@ public class TunnelPoolManager implements TunnelManagerFacade {
         else
             return _outboundExploratory.size(); 
     }
+    public int getInboundClientTunnelCount() { 
+        int count = 0;
+        List destinations = null;
+        synchronized (_clientInboundPools) {
+            destinations = new ArrayList(_clientInboundPools.keySet());
+        }
+        for (int i = 0; i < destinations.size(); i++) {
+            Hash client = (Hash)destinations.get(i);
+            TunnelPool pool = null;
+            synchronized (_clientInboundPools) {
+                pool = (TunnelPool)_clientInboundPools.get(client);
+            }
+            count += pool.listTunnels().size();
+        }
+        return count;
+    }
+    public int getOutboundClientTunnelCount() { 
+        int count = 0;
+        List destinations = null;
+        synchronized (_clientInboundPools) {
+            destinations = new ArrayList(_clientOutboundPools.keySet());
+        }
+        for (int i = 0; i < destinations.size(); i++) {
+            Hash client = (Hash)destinations.get(i);
+            TunnelPool pool = null;
+            synchronized (_clientOutboundPools) {
+                pool = (TunnelPool)_clientOutboundPools.get(client);
+            }
+            count += pool.listTunnels().size();
+        }
+        return count;
+    }
     public int getParticipatingCount() { return _context.tunnelDispatcher().getParticipatingCount(); }
     public long getLastParticipatingExpiration() { return _context.tunnelDispatcher().getLastParticipatingExpiration(); }
     
@@ -240,6 +272,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
             }
         }
         inbound.startup();
+        try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
         outbound.startup();
     }
     
@@ -316,6 +349,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
         _inboundExploratory = new TunnelPool(_context, this, inboundSettings, selector, builder);
         _inboundExploratory.startup();
         
+        try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
         TunnelPoolSettings outboundSettings = new TunnelPoolSettings();
         outboundSettings.setIsExploratory(true);
         outboundSettings.setIsInbound(false);
@@ -406,10 +440,10 @@ public class TunnelPoolManager implements TunnelManagerFacade {
                 out.write("<td>n/a</td>");
             long timeLeft = cfg.getExpiration()-_context.clock().now();
             if (timeLeft > 0)
-                out.write("<td>" + DataHelper.formatDuration(timeLeft) + "</td>");
+                out.write("<td align=right>" + DataHelper.formatDuration(timeLeft) + "</td>");
             else
-                out.write("<td>(grace period)</td>");
-            out.write("<td>" + cfg.getProcessedMessagesCount() + "KB</td>");
+                out.write("<td align=right>(grace period)</td>");
+            out.write("<td align=right>" + cfg.getProcessedMessagesCount() + "KB</td>");
             out.write("</tr>\n");
             processed += cfg.getProcessedMessagesCount();
         }
@@ -441,8 +475,8 @@ public class TunnelPoolManager implements TunnelManagerFacade {
                 out.write("<tr><td><b>inbound</b></td>");
             else
                 out.write("<tr><td><b>outbound</b></td>");
-            out.write("<td>" + DataHelper.formatDuration(timeLeft) + "</td>\n");
-            out.write("<td>" + info.getProcessedMessagesCount() + "KB</td>\n");
+            out.write("<td align=right>" + DataHelper.formatDuration(timeLeft) + "</td>\n");
+            out.write("<td align=right>" + info.getProcessedMessagesCount() + "KB</td>\n");
             for (int j = 0; j < info.getLength(); j++) {
                 Hash peer = info.getPeer(j);
                 TunnelId id = (info.isInbound() ? info.getReceiveTunnelId(j) : info.getSendTunnelId(j));
-- 
GitLab