diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index 6cef36f67b1cfd93109396b8525cca36eb736026..0433ffcbb28e3259fdbc679589f2f64d4f822d0a 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -1,5 +1,6 @@
 package org.klomp.snark;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -393,6 +394,46 @@ public class I2PSnarkUtil {
         }
     }
     
+    /**
+     * Fetch to memory
+     * @param retries if < 0, set timeout to a few seconds
+     * @param initialSize buffer size
+     * @param maxSize fails if greater
+     * @return null on error
+     * @since 0.9.4
+     */
+    public byte[] get(String url, boolean rewrite, int retries, int initialSize, int maxSize) {
+        if (_log.shouldLog(Log.DEBUG))
+            _log.debug("Fetching [" + url + "] to memory");
+        String fetchURL = url;
+        if (rewrite)
+            fetchURL = rewriteAnnounce(url);
+        int timeout;
+        if (retries < 0) {
+            if (!connected())
+                return null;
+            timeout = EEPGET_CONNECT_TIMEOUT_SHORT;
+            retries = 0;
+        } else {
+            timeout = EEPGET_CONNECT_TIMEOUT;
+            if (!connected()) {
+                if (!connect())
+                    return null;
+            }
+        }
+        ByteArrayOutputStream out = new ByteArrayOutputStream(initialSize);
+        EepGet get = new I2PSocketEepGet(_context, _manager, retries, -1, maxSize, null, out, fetchURL);
+        if (get.fetch(timeout)) {
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Fetch successful [" + url + "]: size=" + out.size());
+            return out.toByteArray();
+        } else {
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Fetch failed [" + url + "]");
+            return null;
+        }
+    }
+    
     public I2PServerSocket getServerSocket() { 
         I2PSocketManager mgr = _manager;
         if (mgr != null)
@@ -523,6 +564,15 @@ public class I2PSnarkUtil {
             return Collections.EMPTY_LIST;
         return _openTrackers;
     }
+
+    /**
+     *  List of open trackers to use as backups even if disabled
+     *  @return non-null
+     *  @since 0.9.4
+     */
+    public List<String> getBackupTrackers() { 
+        return _openTrackers;
+    }
     
     public void setUseOpenTrackers(boolean yes) {
         _shouldUseOT = yes;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
index dccfddbcd3513201e5c9fb6df89ede8b43c0c40b..6a821f2ba7a1c511739ec87a2ca5a65ee851049b 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
@@ -20,6 +20,7 @@
 
 package org.klomp.snark;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -43,6 +44,7 @@ import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
 import net.i2p.util.SimpleTimer2;
 
+import org.klomp.snark.bencode.InvalidBEncodingException;
 import org.klomp.snark.dht.DHT;
 
 /**
@@ -70,6 +72,8 @@ public class TrackerClient implements Runnable {
   private static final String COMPLETED_EVENT = "completed";
   private static final String STOPPED_EVENT = "stopped";
   private static final String NOT_REGISTERED  = "torrent not registered"; //bytemonsoon
+  /** this is our equivalent to router.utorrent.com for bootstrap */
+  private static final String DEFAULT_BACKUP_TRACKER = "http://tracker.welterde.i2p/a";
 
   private final static int SLEEP = 5; // 5 minutes.
   private final static int DELAY_MIN = 2000; // 2 secs.
@@ -78,7 +82,7 @@ public class TrackerClient implements Runnable {
   private final static int INITIAL_SLEEP = 90*1000;
   private final static int MAX_CONSEC_FAILS = 5;    // slow down after this
   private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
-  private final static long MIN_TRACKER_ANNOUNCE_INTERVAL = 10*60*1000;
+  private final static long MIN_TRACKER_ANNOUNCE_INTERVAL = 15*60*1000;
   private final static long MIN_DHT_ANNOUNCE_INTERVAL = 10*60*1000;
 
   private final I2PSnarkUtil _util;
@@ -106,6 +110,7 @@ public class TrackerClient implements Runnable {
   private volatile boolean _fastUnannounce;
   private long lastDHTAnnounce;
   private final List<Tracker> trackers;
+  private final List<Tracker> backupTrackers;
 
   /**
    * Call start() to start it.
@@ -131,6 +136,7 @@ public class TrackerClient implements Runnable {
     this.infoHash = urlencode(snark.getInfoHash());
     this.peerID = urlencode(snark.getID());
     this.trackers = new ArrayList(2);
+    this.backupTrackers = new ArrayList(2);
   }
 
   public synchronized void start() {
@@ -233,7 +239,7 @@ public class TrackerClient implements Runnable {
           if (!_initialized) {
               _initialized = true;
               // FIXME only when starting everybody at once, not for a single torrent
-              long delay = I2PAppContext.getGlobalContext().random().nextInt(30*1000);
+              long delay = _util.getContext().random().nextInt(30*1000);
               try {
                   Thread.sleep(delay);
               } catch (InterruptedException ie) {}
@@ -267,18 +273,20 @@ public class TrackerClient implements Runnable {
     if (primary != null) {
         if (isValidAnnounce(primary)) {
             trackers.add(new Tracker(primary, true));
-            _log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
         } else {
-            _log.warn("Skipping invalid or non-i2p announce: " + primary);
+            if (_log.shouldLog(Log.WARN))
+                _log.warn("Skipping invalid or non-i2p announce: " + primary);
         }
     } else {
         _log.warn("No primary announce");
         primary = "";
     }
-    List tlist = _util.getOpenTrackers();
-    if (tlist != null && (meta == null || !meta.isPrivate())) {
+    if (meta == null || !meta.isPrivate()) {
+        List<String> tlist = _util.getOpenTrackers();
         for (int i = 0; i < tlist.size(); i++) {
-             String url = (String)tlist.get(i);
+             String url = tlist.get(i);
              if (!isValidAnnounce(url)) {
                 _log.error("Bad announce URL: [" + url + "]");
                 continue;
@@ -301,9 +309,37 @@ public class TrackerClient implements Runnable {
                 continue;
              // opentrackers are primary if we don't have primary
              trackers.add(new Tracker(url, primary.equals("")));
-             _log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
+             if (_log.shouldLog(Log.DEBUG))
+                 _log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
         }
     }
+
+    // backup trackers if DHT needs bootstrapping
+    if (trackers.isEmpty() && (meta == null || !meta.isPrivate())) {
+        List<String> tlist = _util.getBackupTrackers();
+        for (int i = 0; i < tlist.size(); i++) {
+             String url = tlist.get(i);
+             if (!isValidAnnounce(url)) {
+                _log.error("Bad announce URL: [" + url + "]");
+                continue;
+             }
+             int slash = url.indexOf('/', 7);
+             if (slash <= 7) {
+                _log.error("Bad announce URL: [" + url + "]");
+                continue;
+             }
+             String dest = _util.lookup(url.substring(7, slash));
+             if (dest == null) {
+                _log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
+                continue;
+             }
+             backupTrackers.add(new Tracker(url, false));
+             if (_log.shouldLog(Log.DEBUG))
+                 _log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
+        }
+        if (backupTrackers.isEmpty())
+            backupTrackers.add(new Tracker(DEFAULT_BACKUP_TRACKER, false));
+    }
     this.completed = coordinator.getLeft() == 0;
   }
 
@@ -315,7 +351,7 @@ public class TrackerClient implements Runnable {
   private void loop() {
     try
       {
-        Random r = I2PAppContext.getGlobalContext().random();
+        // normally this will only go once, then call queueLoop() and return
         while(!stop)
           {
             if (!verifyConnected()) {
@@ -325,9 +361,71 @@ public class TrackerClient implements Runnable {
 
             // Local DHT tracker announce
             DHT dht = _util.getDHT();
-            if (dht != null)
+            if (dht != null && (meta == null || !meta.isPrivate()))
                 dht.announce(snark.getInfoHash());
 
+            int maxSeenPeers = 0;
+            if (!trackers.isEmpty())
+                maxSeenPeers = getPeersFromTrackers(trackers);
+            int p = getPeersFromPEX();
+            if (p > maxSeenPeers)
+                maxSeenPeers = p;
+            p = getPeersFromDHT();
+            if (p > maxSeenPeers)
+                maxSeenPeers = p;
+            // backup if DHT needs bootstrapping
+            if (trackers.isEmpty() && !backupTrackers.isEmpty() && dht != null && dht.size() < 16) {
+                p = getPeersFromTrackers(backupTrackers);
+                if (p > maxSeenPeers)
+                    maxSeenPeers = p;
+            }
+
+            // we could try and total the unique peers but that's too hard for now
+            snark.setTrackerSeenPeers(maxSeenPeers);
+
+            if (stop)
+                return;
+
+            try {
+                // Sleep some minutes...
+                // Sleep the minimum interval for all the trackers, but 60s minimum
+                int delay;
+                Random r = _util.getContext().random();
+                int random = r.nextInt(120*1000);
+                if (completed && runStarted)
+                  delay = 3*SLEEP*60*1000 + random;
+                else if (snark.getTrackerProblems() != null && ++consecutiveFails < MAX_CONSEC_FAILS)
+                  delay = INITIAL_SLEEP;
+                else if ((!runStarted) && _runCount < MAX_CONSEC_FAILS)
+                  delay = INITIAL_SLEEP;
+                else
+                  // sleep a while, when we wake up we will contact only the trackers whose intervals have passed
+                  delay = SLEEP*60*1000 + random;
+
+                if (delay > 20*1000) {
+                  // put ourselves on SimpleTimer2
+                  if (_log.shouldLog(Log.DEBUG))
+                      _log.debug("Requeueing in " + DataHelper.formatDuration(delay) + ": " + Thread.currentThread().getName());
+                  queueLoop(delay);
+                  return;
+                } else if (delay > 0) {
+                  Thread.sleep(delay);
+                }
+              } catch(InterruptedException interrupt) {}
+          } // *** end of while loop
+      } // try
+    catch (Throwable t)
+      {
+        _log.error("TrackerClient: " + t, t);
+        if (t instanceof OutOfMemoryError)
+            throw (OutOfMemoryError)t;
+      }
+  }
+
+  /**
+   *  @return max peers seen
+   */
+  private int getPeersFromTrackers(List<Tracker> trckrs) {
             long uploaded = coordinator.getUploaded();
             long downloaded = coordinator.getDownloaded();
             long left = coordinator.getLeft();   // -1 in magnet mode
@@ -341,10 +439,10 @@ public class TrackerClient implements Runnable {
               }
             else
               event = NO_EVENT;
-            
+
             // *** loop once for each tracker
             int maxSeenPeers = 0;
-            for (Tracker tr : trackers) {
+            for (Tracker tr : trckrs) {
               if ((!stop) && (!tr.stop) &&
                   (completed || coordinator.needOutboundPeers() || !tr.started) &&
                   (event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
@@ -372,7 +470,7 @@ public class TrackerClient implements Runnable {
                         snark.setTrackerSeenPeers(tr.seenPeers);
 
                     // pass everybody over to our tracker
-                    dht = _util.getDHT();
+                    DHT dht = _util.getDHT();
                     if (dht != null) {
                         for (Peer peer : peers) {
                             dht.announce(snark.getInfoHash(), peer.getPeerID().getDestHash());
@@ -383,6 +481,7 @@ public class TrackerClient implements Runnable {
                         // we only want to talk to new people if we need things
                         // from them (duh)
                         List<Peer> ordered = new ArrayList(peers);
+                        Random r = _util.getContext().random();
                         Collections.shuffle(ordered, r);
                         Iterator<Peer> it = ordered.iterator();
                         while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
@@ -409,13 +508,13 @@ public class TrackerClient implements Runnable {
                       snark.setTrackerProblems(tr.trackerProblems);
                     if (tr.trackerProblems.toLowerCase(Locale.US).startsWith(NOT_REGISTERED)) {
                       // Give a guy some time to register it if using opentrackers too
-                      if (trackers.size() == 1) {
-                        stop = true;
-                        snark.stopTorrent();
-                      } else { // hopefully each on the opentrackers list is really open
+                      //if (trckrs.size() == 1) {
+                      //  stop = true;
+                      //  snark.stopTorrent();
+                      //} else { // hopefully each on the opentrackers list is really open
                         if (tr.registerFails++ > MAX_REGISTER_FAILS)
                           tr.stop = true;
-                      }
+                      //
                     }
                     if (++tr.consecutiveFails == MAX_CONSEC_FAILS) {
                         tr.seenPeers = 0;
@@ -432,7 +531,15 @@ public class TrackerClient implements Runnable {
                   maxSeenPeers = tr.seenPeers;
             }  // *** end of trackers loop here
 
+            return maxSeenPeers;
+  }
+
+  /**
+   *  @return max peers seen
+   */
+  private int getPeersFromPEX() {
             // Get peers from PEX
+            int rv = 0;
             if (coordinator.needOutboundPeers() && (meta == null || !meta.isPrivate()) && !stop) {
                 Set<PeerID> pids = coordinator.getPEXPeers();
                 if (!pids.isEmpty()) {
@@ -442,6 +549,7 @@ public class TrackerClient implements Runnable {
                     for (PeerID pID : pids) {
                         peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
                     }
+                    Random r = _util.getContext().random();
                     Collections.shuffle(peers, r);
                     Iterator<Peer> it = peers.iterator();
                     while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
@@ -451,19 +559,27 @@ public class TrackerClient implements Runnable {
                             try { Thread.sleep(delay); } catch (InterruptedException ie) {}
                          }
                     }
+                    rv = pids.size();
                 }
             } else {
                 if (_log.shouldLog(Log.INFO))
                     _log.info("Not getting PEX peers");
             }
+            return rv;
+    }
 
+  /**
+   *  @return max peers seen
+   */
+  private int getPeersFromDHT() {
             // Get peers from DHT
             // FIXME this needs to be in its own thread
-            dht = _util.getDHT();
+            int rv = 0;
+            DHT dht = _util.getDHT();
             if (dht != null && (meta == null || !meta.isPrivate()) && (!stop) &&
                 _util.getContext().clock().now() >  lastDHTAnnounce + MIN_DHT_ANNOUNCE_INTERVAL) {
                 int numwant;
-                if (event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers())
+                if (!coordinator.needOutboundPeers())
                     numwant = 1;
                 else
                     numwant = _util.getMaxConnections();
@@ -471,6 +587,7 @@ public class TrackerClient implements Runnable {
                 if (!hashes.isEmpty()) {
                     runStarted = true;
                     lastDHTAnnounce = _util.getContext().clock().now();
+                    rv = hashes.size();
                 }
                 if (_log.shouldLog(Log.INFO))
                     _log.info("Got " + hashes + " from DHT");
@@ -487,9 +604,12 @@ public class TrackerClient implements Runnable {
                 if ((!stop) && !hashes.isEmpty()) {
                     List<Peer> peers = new ArrayList(hashes.size());
                     for (Hash h : hashes) {
-                        PeerID pID = new PeerID(h.getData(), _util);
-                        peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
+                        try {
+                            PeerID pID = new PeerID(h.getData(), _util);
+                            peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
+                        } catch (InvalidBEncodingException ibe) {}
                     }
+                    Random r = _util.getContext().random();
                     Collections.shuffle(peers, r);
                     Iterator<Peer> it = peers.iterator();
                     while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
@@ -504,49 +624,10 @@ public class TrackerClient implements Runnable {
                 if (_log.shouldLog(Log.INFO))
                     _log.info("Not getting DHT peers");
             }
-
-
-            // we could try and total the unique peers but that's too hard for now
-            snark.setTrackerSeenPeers(maxSeenPeers);
-
-            if (stop)
-                return;
-
-            try {
-                // Sleep some minutes...
-                // Sleep the minimum interval for all the trackers, but 60s minimum
-                int delay;
-                int random = r.nextInt(120*1000);
-                if (completed && runStarted)
-                  delay = 3*SLEEP*60*1000 + random;
-                else if (snark.getTrackerProblems() != null && ++consecutiveFails < MAX_CONSEC_FAILS)
-                  delay = INITIAL_SLEEP;
-                else if ((!runStarted) && _runCount < MAX_CONSEC_FAILS)
-                  delay = INITIAL_SLEEP;
-                else
-                  // sleep a while, when we wake up we will contact only the trackers whose intervals have passed
-                  delay = SLEEP*60*1000 + random;
-
-                if (delay > 20*1000) {
-                  // put ourselves on SimpleTimer2
-                  if (_log.shouldLog(Log.DEBUG))
-                      _log.debug("Requeueing in " + DataHelper.formatDuration(delay) + ": " + Thread.currentThread().getName());
-                  queueLoop(delay);
-                  return;
-                } else if (delay > 0) {
-                  Thread.sleep(delay);
-                }
-              } catch(InterruptedException interrupt) {}
-          } // *** end of while loop
-      } // try
-    catch (Throwable t)
-      {
-        _log.error("TrackerClient: " + t, t);
-        if (t instanceof OutOfMemoryError)
-            throw (OutOfMemoryError)t;
-      }
+            return rv;
   }
 
+
   /**
    *  Creates a thread for each tracker in parallel if tunnel is still open
    *  @since 0.9.1
@@ -630,7 +711,8 @@ public class TrackerClient implements Runnable {
     if (! event.equals(NO_EVENT))
         buf.append("&event=").append(event);
     buf.append("&numwant=");
-    if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers())
+    boolean small = left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers();
+    if (small)
         buf.append('0');
     else
         buf.append(_util.getMaxConnections());
@@ -641,14 +723,12 @@ public class TrackerClient implements Runnable {
     tr.lastRequestTime = System.currentTimeMillis();
     // Don't wait for a response to stopped when shutting down
     boolean fast = _fastUnannounce && event.equals(STOPPED_EVENT);
-    File fetched = _util.get(s, true, fast ? -1 : 0);
+    byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 8*1024);
     if (fetched == null) {
         throw new IOException("Error fetching " + s);
     }
     
-    InputStream in = null;
-    try {
-        in = new FileInputStream(fetched);
+        InputStream in = new ByteArrayInputStream(fetched);
 
         TrackerInfo info = new TrackerInfo(in, snark.getID(),
                                            snark.getInfoHash(), snark.getMetaInfo(), _util);
@@ -661,10 +741,6 @@ public class TrackerClient implements Runnable {
 
         tr.interval = Math.max(MIN_TRACKER_ANNOUNCE_INTERVAL, info.getInterval() * 1000l);
         return info;
-    } finally {
-        if (in != null) try { in.close(); } catch (IOException ioe) {}
-        fetched.delete();
-    }
   }
 
   /**
diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java
index 55521a7876f081fdaf00937c1ef801aa4643c969..b94c5f555fb5ff933ce5efca54c2a1e75be388f0 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java
@@ -143,6 +143,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
     /** how long since generated do we delete - BEP 5 says 10 minutes */
     private static final long MAX_TOKEN_AGE = 10*60*1000;
     private static final long MAX_INBOUND_TOKEN_AGE = MAX_TOKEN_AGE - 2*60*1000;
+    private static final int MAX_OUTBOUND_TOKENS = 5000;
     /** how long since sent do we wait for a reply */
     private static final long MAX_MSGID_AGE = 2*60*1000;
     /** how long since sent do we wait for a reply */
@@ -1208,7 +1209,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
 
     /**
      *  Handle and respond to the query.
-     *  We have no node info here, it came on response port, we have to get it from the token
+     *  We have no node info here, it came on response port, we have to get it from the token.
+     *  So we can't verify that it came from the same peer, as BEP 5 specifies.
      */
     private void receiveAnnouncePeer(MsgID msgID, InfoHash ih, byte[] tok) throws InvalidBEncodingException {
         Token token = new Token(tok);
@@ -1216,8 +1218,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
         if (nInfo == null) {
             if (_log.shouldLog(Log.WARN))
                 _log.warn("Unknown token in announce_peer: " + token);
-            if (_log.shouldLog(Log.INFO))
-                _log.info("Current known tokens: " + _outgoingTokens.keySet());
+            //if (_log.shouldLog(Log.INFO))
+            //    _log.info("Current known tokens: " + _outgoingTokens.keySet());
             return;
         }
         if (_log.shouldLog(Log.INFO))
@@ -1282,8 +1284,9 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
      *  @throws NPE, IllegalArgumentException, and others too
      */
     private List<NodeInfo> receiveNodes(NodeInfo nInfo, byte[] ids) throws InvalidBEncodingException {
-        List<NodeInfo> rv = new ArrayList(ids.length / NodeInfo.LENGTH);
-        for (int off = 0; off < ids.length; off += NodeInfo.LENGTH) {
+        int max = Math.min(K, ids.length / NodeInfo.LENGTH);
+        List<NodeInfo> rv = new ArrayList(max);
+        for (int off = 0; off < ids.length && rv.size() < max; off += NodeInfo.LENGTH) {
             NodeInfo nInf = new NodeInfo(ids, off);
             if (_blacklist.contains(nInf.getNID())) {
                 if (_log.shouldLog(Log.INFO))
@@ -1305,12 +1308,15 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
     private List<Hash> receivePeers(NodeInfo nInfo, List<BEValue> peers) throws InvalidBEncodingException {
         if (_log.shouldLog(Log.INFO))
              _log.info("Rcvd peers from: " + nInfo);
-        List<Hash> rv = new ArrayList(peers.size());
+        int max = Math.min(MAX_WANT, peers.size());
+        List<Hash> rv = new ArrayList(max);
         for (BEValue bev : peers) {
             byte[] b = bev.getBytes();
             //Hash h = new Hash(b);
             Hash h = Hash.create(b);
             rv.add(h);
+            if (rv.size() >= max)
+                break;
         }
         if (_log.shouldLog(Log.INFO))
              _log.info("Rcvd peers from: " + nInfo + ": " + DataHelper.toString(rv));
@@ -1535,20 +1541,28 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
                           _blacklist.size() + " in blacklist, " +
                           _outgoingTokens.size() + " sent Tokens, " +
                           _incomingTokens.size() + " rcvd Tokens");
+            int cnt = 0;
+            long expire = now - MAX_TOKEN_AGE;
             for (Iterator<Token> iter = _outgoingTokens.keySet().iterator(); iter.hasNext(); ) {
                 Token tok = iter.next();
-                if (tok.lastSeen() < now - MAX_TOKEN_AGE)
+                // just delete at random if we have too many
+                // TODO reduce the expire time and iterate again?
+                if (tok.lastSeen() < expire || cnt >= MAX_OUTBOUND_TOKENS)
                     iter.remove();
+                else
+                    cnt++;
             }
+            expire = now - MAX_INBOUND_TOKEN_AGE;
             for (Iterator<Token> iter = _incomingTokens.values().iterator(); iter.hasNext(); ) {
                 Token tok = iter.next();
-                if (tok.lastSeen() < now - MAX_INBOUND_TOKEN_AGE)
+                if (tok.lastSeen() < expire)
                     iter.remove();
             }
+            expire = now - BLACKLIST_CLEAN_TIME;
             for (Iterator<NID> iter = _blacklist.iterator(); iter.hasNext(); ) {
                 NID nid = iter.next();
                 // lastSeen() is actually when-added
-                if (now > nid.lastSeen() + BLACKLIST_CLEAN_TIME)
+                if (nid.lastSeen() < expire)
                     iter.remove();
             }
             // TODO sent queries?
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
index eb797b9a1bf1e9a3779efe0d308eeb21e02b5e90..e010714c12ff6d80394f5635a4a9d51f3e770811 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/UnsignedUpdateRunner.java
@@ -53,6 +53,8 @@ class UnsignedUpdateRunner extends UpdateRunner {
             } catch (Throwable t) {
                 _log.error("Error updating", t);
             }
+            if (!this.done)
+                _mgr.notifyTaskFailed(this, "", null);
         }
         
         /** eepget listener callback Overrides */
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
index 785af69a3e3f980b395ddfe364d6312120eee32a..3d92f6d2ab390e00b382b88963b3f06aea21294d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java
@@ -3,6 +3,8 @@ package net.i2p.router.web;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import net.i2p.util.RandomSource;
+
 /**
  * Copied and modded from I2PTunnel IndexBean (GPL)
  * @author zzz
@@ -11,8 +13,6 @@ public class CSSHelper extends HelperBase {
 
     private static final Map<String, Boolean> _UACache = new ConcurrentHashMap();
 
-    public CSSHelper() {}
-
     public static final String PROP_UNIVERSAL_THEMING = "routerconsole.universal.theme";
     public static final String PROP_THEME_NAME = "routerconsole.theme";
     public static final String DEFAULT_THEME = "light";
@@ -24,6 +24,16 @@ public class CSSHelper extends HelperBase {
     public static final String PROP_DISABLE_REFRESH = "routerconsole.summaryDisableRefresh";
     private static final String PROP_XFRAME = "routerconsole.disableXFrame";
 
+    private static final String _consoleNonce = Long.toString(RandomSource.getInstance().nextLong());
+
+    /**
+     *  formerly stored in System.getProperty("router.consoleNonce")
+     *  @since 0.9.4
+     */
+    public static String getNonce() { 
+        return _consoleNonce;
+    }
+
     public String getTheme(String userAgent) {
         String url = BASE_THEME_PATH;
         if (userAgent != null && userAgent.contains("MSIE")) {
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
index 204bda9f829a961dd9f30e40ec205689e9d197bb..836d07ea368d428d868c895d93900e041615eea9 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigRestartBean.java
@@ -3,6 +3,7 @@ package net.i2p.router.web;
 import net.i2p.data.DataHelper;
 import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
+import net.i2p.util.RandomSource;
 
 /**
  * simple helper to control restarts/shutdowns in the left hand nav
@@ -12,20 +13,18 @@ public class ConfigRestartBean {
     /** all these are tagged below so no need to _x them here.
      *  order is: form value, form class, display text.
      */
-    static final String[] SET1 = {"shutdownImmediate", "stop", "Shutdown immediately", "cancelShutdown", "cancel", "Cancel shutdown"};
-    static final String[] SET2 = {"restartImmediate", "reload", "Restart immediately", "cancelShutdown", "cancel", "Cancel restart"};
-    static final String[] SET3 = {"restart", "reload", "Restart", "shutdown", "stop", "Shutdown"};
-    static final String[] SET4 = {"shutdown", "stop", "Shutdown"};
+    private static final String[] SET1 = {"shutdownImmediate", "stop", "Shutdown immediately", "cancelShutdown", "cancel", "Cancel shutdown"};
+    private static final String[] SET2 = {"restartImmediate", "reload", "Restart immediately", "cancelShutdown", "cancel", "Cancel restart"};
+    private static final String[] SET3 = {"restart", "reload", "Restart", "shutdown", "stop", "Shutdown"};
+    private static final String[] SET4 = {"shutdown", "stop", "Shutdown"};
 
+    private static final String _systemNonce = Long.toString(RandomSource.getInstance().nextLong());
+
+    /** formerly System.getProperty("console.nonce") */
     public static String getNonce() { 
-        RouterContext ctx = ContextHelper.getContext(null);
-        String nonce = System.getProperty("console.nonce");
-        if (nonce == null) {
-            nonce = ""+ctx.random().nextLong();
-            System.setProperty("console.nonce", nonce);
-        }
-        return nonce;
+        return _systemNonce;
     }
+
     public static String renderStatus(String urlBase, String action, String nonce) {
         RouterContext ctx = ContextHelper.getContext(null);
         String systemNonce = getNonce();
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java
index ba5856632a851e85f49bb391cfb14ff79ff4eb37..ebd1adc07ec26f0300f7f842109509f6df829228 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/FormHandler.java
@@ -194,8 +194,8 @@ public class FormHandler {
             return;
         }
         
-        String sharedNonce = System.getProperty("router.consoleNonce");
-        if ( (sharedNonce != null) && (sharedNonce.equals(_nonce) ) ) {
+        String sharedNonce = CSSHelper.getNonce();
+        if (sharedNonce.equals(_nonce)) {
             return;
         }
         
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
index 5e5fcecfe4e8b55e67351bdbd89041afcda7842d..162290e9472e3dfb63f73b32df3020a951ea09cd 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java
@@ -212,7 +212,7 @@ public class NewsHelper extends ContentHelper {
                                            ctx));
          }
          buf.append("</i>");
-         String consoleNonce = System.getProperty("router.consoleNonce");
+         String consoleNonce = CSSHelper.getNonce();
          if (lastUpdated > 0 && consoleNonce != null) {
              if (shouldShowNews(ctx)) {
                  buf.append(" <a href=\"/?news=0&amp;consoleNonce=").append(consoleNonce).append("\">")
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
index 61bed45480e46c53949aaaf57ac326e9ee9b738b..496c41cab0839b981dec4b0f79ca8f58c7cf20d1 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java
@@ -606,7 +606,7 @@ public class SummaryBarRenderer {
         NewsHelper newshelper = _helper.getNewsHelper();
         if (newshelper == null || newshelper.shouldShowNews()) return "";
         StringBuilder buf = new StringBuilder(512);
-        String consoleNonce = System.getProperty("router.consoleNonce");
+        String consoleNonce = CSSHelper.getNonce();
         if (consoleNonce != null) {
             // Set up title and pre-headings stuff.
             buf.append("<h3><a href=\"/configupdate\">")
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 2bd763606f9cd33f70dc8b4b2494f7ba985281bc..3015f23e94c7e2f7730aed9d02063daa7ef484af 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java
@@ -809,7 +809,13 @@ public class SummaryHelper extends HelperBase {
 
     private String _requestURI;
     public void setRequestURI(String s) { _requestURI = s; }
-    public String getRequestURI() { return _requestURI; }
+
+    /**
+     * @return non-null; "/home" if (strangely) not set by jsp
+     */
+    public String getRequestURI() {
+        return _requestURI != null ? _requestURI : "/home";
+    }
 
     public String getConfigTable() {
         String[] allSections = SummaryBarRenderer.ALL_SECTIONS;
diff --git a/apps/routerconsole/jsp/configui.jsp b/apps/routerconsole/jsp/configui.jsp
index f1555a8920d1b99430144430c2030555ca810054..b19c0103caa81f49233d6f312fb26a5f3da12019 100644
--- a/apps/routerconsole/jsp/configui.jsp
+++ b/apps/routerconsole/jsp/configui.jsp
@@ -31,15 +31,7 @@ input.default {
 <%@include file="formhandler.jsi" %>
 <div class="configure"><div class="topshimten"><h3><%=uihelper._("Router Console Theme")%></h3></div>
  <form action="" method="POST">
-<%
-    /** lang setting is done in css.jsi, not in ConfigUIHandler */
-    String consoleNonce = System.getProperty("router.consoleNonce");
-    if (consoleNonce == null) {
-        consoleNonce = Long.toString(new java.util.Random().nextLong());
-        System.setProperty("router.consoleNonce", consoleNonce);
-    }
-%>
- <input type="hidden" name="consoleNonce" value="<%=consoleNonce%>" >
+ <input type="hidden" name="consoleNonce" value="<%=intl.getNonce()%>" >
  <input type="hidden" name="nonce" value="<%=pageNonce%>" >
  <input type="hidden" name="action" value="blah" >
 <%
diff --git a/apps/routerconsole/jsp/console.jsp b/apps/routerconsole/jsp/console.jsp
index b70c4170eab4835353d2e850d6024a1177851ca5..883c83c56e452be29bde7341b0b3e0f00104dd10 100644
--- a/apps/routerconsole/jsp/console.jsp
+++ b/apps/routerconsole/jsp/console.jsp
@@ -10,11 +10,7 @@
 <%@include file="summaryajax.jsi" %>
 </head><body onload="initAjax()">
 <%
-    String consoleNonce = System.getProperty("router.consoleNonce");
-    if (consoleNonce == null) {
-        consoleNonce = Long.toString(new java.util.Random().nextLong());
-        System.setProperty("router.consoleNonce", consoleNonce);
-    }
+    String consoleNonce = intl.getNonce();
 %>
 
 <%@include file="summary.jsi" %>
diff --git a/apps/routerconsole/jsp/css.jsi b/apps/routerconsole/jsp/css.jsi
index 437bf015ea495121592992faafa2b31e1c01ef31..21dc7eecc25905aaa32ee4ffa33140540b602c1a 100644
--- a/apps/routerconsole/jsp/css.jsi
+++ b/apps/routerconsole/jsp/css.jsi
@@ -34,7 +34,7 @@
       response.setHeader("X-Frame-Options", "SAMEORIGIN");
 
    String conNonceParam = request.getParameter("consoleNonce");
-   if (conNonceParam != null && conNonceParam.equals(System.getProperty("router.consoleNonce"))) {
+   if (intl.getNonce().equals(conNonceParam)) {
        intl.setLang(request.getParameter("lang"));
        intl.setNews(request.getParameter("news"));
    }
diff --git a/apps/routerconsole/jsp/home.jsp b/apps/routerconsole/jsp/home.jsp
index 0971ac868402d110e290ba90e222b4b754659174..4423bed83cc05fb44ee9f332f0fc34e8bbfb36c2 100644
--- a/apps/routerconsole/jsp/home.jsp
+++ b/apps/routerconsole/jsp/home.jsp
@@ -8,11 +8,7 @@
 <%@include file="summaryajax.jsi" %>
 </head><body onload="initAjax()">
 <%
-    String consoleNonce = System.getProperty("router.consoleNonce");
-    if (consoleNonce == null) {
-        consoleNonce = Long.toString(new java.util.Random().nextLong());
-        System.setProperty("router.consoleNonce", consoleNonce);
-    }
+    String consoleNonce = intl.getNonce();
 %>
 <jsp:useBean class="net.i2p.router.web.NewsHelper" id="newshelper" scope="request" />
 <jsp:setProperty name="newshelper" property="contextId" value="<%=(String)session.getAttribute(\"i2p.contextId\")%>" />
diff --git a/history.txt b/history.txt
index 634e385b05e7324bc1d4e181ca79020dd69b8f9f..783e80d9252ed522b26bc818bfe8d66ba3f4d663 100644
--- a/history.txt
+++ b/history.txt
@@ -1,6 +1,20 @@
+2012-11-05 zzz
+ * Console:
+   - Fix NPE after restart (ticket #763)
+   - Move more nonces out of system properties
+ * i2psnark:
+   - More DHT limits
+   - Announce to backup trackers if DHT is empty
+   - Use PEX and DHT info in torrent peer count
+   - Don't use temp files for announces
+ * PeerManager: Don't reorganize as often if it takes too long (ticket #765)
+ * RequestLeaseSetJob: Only disconnect client after multiple dropped
+   lease set requests; reduce timeout, other cleanups
+ * Unsigned Update: Fix notification on failure
+
 2012-11-02 kytv
-* German, Portuguese, and Swedish translation updates from Transifex
-* Refreshed English po files to push to TX.
+ * German, Portuguese, and Swedish translation updates from Transifex
+ * Refreshed English po files to push to TX.
 
 2012-11-02 zzz
  * configstats: Fix group sorting, translate groups
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index f6e6df3a5c0d10fbf1efc2f7069c342c2fe16f4e..4e28a2c8d2676ff98ee569a93ae883ef407d9461 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 4;
+    public final static long BUILD = 5;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
index cfe23fa9863cb2f74b81df72314d5e0f97f1fca3..36b3689062e4c3f6e95aa2c84caacf8896ece5a8 100644
--- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
+++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
@@ -71,6 +71,7 @@ class ClientConnectionRunner {
     private final Map<MessageId, Payload> _messages; 
     /** lease set request state, or null if there is no request pending on at the moment */
     private LeaseRequestState _leaseRequest;
+    private int _consecutiveLeaseRequestFails;
     /** currently allocated leaseSet, or null if none is allocated */
     private LeaseSet _currentLeaseSet;
     /** set of messageIds created but not yet ACCEPTED */
@@ -100,6 +101,7 @@ class ClientConnectionRunner {
     // e.g. on local access
     private static final int MAX_MESSAGE_ID = 0x4000000;
 
+    private static final int MAX_LEASE_FAILS = 5;
     private static final int BUF_SIZE = 32*1024;
 
     /** @since 0.9.2 */
@@ -191,12 +193,17 @@ class ClientConnectionRunner {
     /** data for the current leaseRequest, or null if there is no active leaseSet request */
     LeaseRequestState getLeaseRequest() { return _leaseRequest; }
 
-    void setLeaseRequest(LeaseRequestState req) { 
+    /** @param req non-null */
+    public void failLeaseRequest(LeaseRequestState req) { 
+        boolean disconnect = false;
         synchronized (this) {
-            if ( (_leaseRequest != null) && (req != _leaseRequest) )
-                _log.error("Changing leaseRequest from " + _leaseRequest + " to " + req);
-            _leaseRequest = req; 
+            if (_leaseRequest == req) {
+                _leaseRequest = null;
+                disconnect = ++_consecutiveLeaseRequestFails > MAX_LEASE_FAILS;
+            }
         }
+        if (disconnect)
+            disconnectClient("Too many leaseset request fails");
     }
 
     /** already closed? */
@@ -287,6 +294,7 @@ class ClientConnectionRunner {
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("LeaseSet created fully: " + state + " / " + ls);
                 _leaseRequest = null;
+                _consecutiveLeaseRequestFails = 0;
             }
         }
         if ( (state != null) && (state.getOnGranted() != null) )
@@ -351,9 +359,8 @@ class ClientConnectionRunner {
             _acceptedPending.add(id);
 
         if (_log.shouldLog(Log.DEBUG))
-            _log.debug("** Receiving message [" + id.getMessageId() + "] with payload of size [" 
-                       + payload.getSize() + "]" + " for session [" + _sessionId.getSessionId() 
-                       + "]");
+            _log.debug("** Receiving message " + id.getMessageId() + " with payload of size " 
+                       + payload.getSize() + " for session " + _sessionId.getSessionId());
         //long beforeDistribute = _context.clock().now();
         // the following blocks as described above
         SessionConfig cfg = _config;
@@ -380,7 +387,7 @@ class ClientConnectionRunner {
         if (sid == null) return;
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Acking message send [accepted]" + id + " / " + nonce + " for sessionId " 
-                       + sid, new Exception("sendAccepted"));
+                       + sid);
         MessageStatusMessage status = new MessageStatusMessage();
         status.setMessageId(id.getMessageId()); 
         status.setSessionId(sid.getSessionId());
@@ -486,10 +493,11 @@ class ClientConnectionRunner {
                 return; // already requesting
             } else {
                 _leaseRequest = state = new LeaseRequestState(onCreateJob, onFailedJob, _context.clock().now() + expirationTime, set);
-                _log.debug("Not already requesting, continue to request " + set);
+                if (_log.shouldLog(Log.DEBUG))
+                    _log.debug("New request: " + state);
             }
         }
-        _context.jobQueue().addJob(new RequestLeaseSetJob(_context, this, set, _context.clock().now() + expirationTime, onCreateJob, onFailedJob, state));
+        _context.jobQueue().addJob(new RequestLeaseSetJob(_context, this, state));
     }
 
     private class Rerequest implements SimpleTimer.TimedEvent {
@@ -646,8 +654,8 @@ class ClientConnectionRunner {
             if (!alreadyAccepted(_messageId)) {
                 _log.warn("Almost send an update for message " + _messageId + " to " 
                           + MessageStatusMessage.getStatusString(msg.getStatus()) 
-                          + " for session [" + _sessionId.getSessionId() 
-                          + "] before they knew the messageId!  delaying .5s");
+                          + " for session " + _sessionId.getSessionId() 
+                          + " before they knew the messageId!  delaying .5s");
                 _lastTried = _context.clock().now();
                 requeue(REQUEUE_DELAY);
                 return;
@@ -680,15 +688,14 @@ class ClientConnectionRunner {
                 if (_log.shouldLog(Log.DEBUG))
                     _log.info("Updating message status for message " + _messageId + " to " 
                               + MessageStatusMessage.getStatusString(msg.getStatus()) 
-                              + " for session [" + _sessionId.getSessionId() 
-                              + "] (with nonce=2), retrying after [" 
-                              + (_context.clock().now() - _lastTried) 
-                              + "]");
+                              + " for session " + _sessionId.getSessionId() 
+                              + " (with nonce=2), retrying after " 
+                              + (_context.clock().now() - _lastTried));
             } else {
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("Updating message status for message " + _messageId + " to " 
                                + MessageStatusMessage.getStatusString(msg.getStatus()) 
-                               + " for session [" + _sessionId.getSessionId() + "] (with nonce=2)");
+                               + " for session " + _sessionId.getSessionId() + " (with nonce=2)");
             }
 
             try {
diff --git a/router/java/src/net/i2p/router/client/ClientManager.java b/router/java/src/net/i2p/router/client/ClientManager.java
index c1816bfd0287ad95d58f3b64e86a813e1844d764..1a07da10e069a9152684ca031af1e9cf661098b2 100644
--- a/router/java/src/net/i2p/router/client/ClientManager.java
+++ b/router/java/src/net/i2p/router/client/ClientManager.java
@@ -62,6 +62,8 @@ class ClientManager {
 
     private static final int INTERNAL_QUEUE_SIZE = 256;
 
+    private static final long REQUEST_LEASESET_TIMEOUT = 60*1000;
+
     public ClientManager(RouterContext context, int port) {
         _ctx = context;
         _log = context.logManager().getLog(ClientManager.class);
@@ -275,6 +277,8 @@ class ClientManager {
      * within the timeout specified, queue up the onFailedJob.  This call does not
      * block.
      *
+     * UNUSED, the call below without jobs is always used.
+     *
      * @param dest Destination from which the LeaseSet's authorization should be requested
      * @param set LeaseSet with requested leases - this object must be updated to contain the 
      *            signed version (as well as any changed/added/removed Leases)
@@ -290,12 +294,10 @@ class ClientManager {
                           + dest.calculateHash().toBase64() + ".  disconnected?");
             _ctx.jobQueue().addJob(onFailedJob);
         } else {
-            runner.requestLeaseSet(set, _ctx.clock().now() + timeout, onCreateJob, onFailedJob);
+            runner.requestLeaseSet(set, timeout, onCreateJob, onFailedJob);
         }
     }
 
-    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/ClientManagerFacadeImpl.java b/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java
index e422e99177965f23120316e3c0c7df10dcf947dc..ff40c211599a149e63015d99faa2bc52421a7694 100644
--- a/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java
+++ b/router/java/src/net/i2p/router/client/ClientManagerFacadeImpl.java
@@ -110,6 +110,8 @@ public class ClientManagerFacadeImpl extends ClientManagerFacade implements Inte
      * within the timeout specified, queue up the onFailedJob.  This call does not
      * block.
      *
+     * UNUSED, the call below without jobs is always used.
+     *
      * @param dest Destination from which the LeaseSet's authorization should be requested
      * @param set LeaseSet with requested leases - this object must be updated to contain the 
      *            signed version (as well as any changed/added/removed Leases)
diff --git a/router/java/src/net/i2p/router/client/LeaseRequestState.java b/router/java/src/net/i2p/router/client/LeaseRequestState.java
index 50f4003ca1c9a36d93ce8dbbe5990fee2af0ee73..67968df3b945df7c0bbb0de84569d160b5a12d30 100644
--- a/router/java/src/net/i2p/router/client/LeaseRequestState.java
+++ b/router/java/src/net/i2p/router/client/LeaseRequestState.java
@@ -28,6 +28,9 @@ class LeaseRequestState {
     private final long _expiration;
     private boolean _successful;
 
+    /**
+     *  @param expiration absolute time
+     */
     public LeaseRequestState(Job onGranted, Job onFailed, long expiration, LeaseSet requested) {
         _onGranted = onGranted;
         _onFailed = onFailed;
diff --git a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
index 1abb54c887c262b153e052995023ec400e85cc31..72147c060c8a59bb1cba0349214b010dc0ffddc7 100644
--- a/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
+++ b/router/java/src/net/i2p/router/client/RequestLeaseSetJob.java
@@ -31,7 +31,7 @@ class RequestLeaseSetJob extends JobImpl {
     private final ClientConnectionRunner _runner;
     private final LeaseRequestState _requestState;
     
-    public RequestLeaseSetJob(RouterContext ctx, ClientConnectionRunner runner, LeaseSet set, long expiration, Job onCreate, Job onFail, LeaseRequestState state) {
+    public RequestLeaseSetJob(RouterContext ctx, ClientConnectionRunner runner, LeaseRequestState state) {
         super(ctx);
         _log = ctx.logManager().getLog(RequestLeaseSetJob.class);
         _runner = runner;
@@ -42,6 +42,7 @@ class RequestLeaseSetJob extends JobImpl {
     }
     
     public String getName() { return "Request Lease Set"; }
+
     public void runJob() {
         if (_runner.isDead()) return;
         
@@ -59,21 +60,23 @@ class RequestLeaseSetJob extends JobImpl {
         msg.setSessionId(_runner.getSessionId());
         
         for (int i = 0; i < _requestState.getRequested().getLeaseCount(); i++) {
-            msg.addEndpoint(_requestState.getRequested().getLease(i).getGateway(), _requestState.getRequested().getLease(i).getTunnelId());
+            msg.addEndpoint(_requestState.getRequested().getLease(i).getGateway(),
+                            _requestState.getRequested().getLease(i).getTunnelId());
         }
         
         try {
             //_runner.setLeaseRequest(state);
             _runner.doSend(msg);
-            getContext().jobQueue().addJob(new CheckLeaseRequestStatus(getContext(), _requestState));
-            return;
+            getContext().jobQueue().addJob(new CheckLeaseRequestStatus());
         } catch (I2CPMessageException ime) {
             getContext().statManager().addRateData("client.requestLeaseSetDropped", 1, 0);
             _log.error("Error sending I2CP message requesting the lease set", ime);
             _requestState.setIsSuccessful(false);
-            _runner.setLeaseRequest(null);
-            _runner.disconnectClient("I2CP error requesting leaseSet");
-            return;
+            if (_requestState.getOnFailed() != null)
+                RequestLeaseSetJob.this.getContext().jobQueue().addJob(_requestState.getOnFailed());
+            _runner.failLeaseRequest(_requestState);
+            // Don't disconnect, the tunnel will retry
+            //_runner.disconnectClient("I2CP error requesting leaseSet");
         }
     }
     
@@ -84,14 +87,12 @@ class RequestLeaseSetJob extends JobImpl {
      *
      */
     private class CheckLeaseRequestStatus extends JobImpl {
-        private final LeaseRequestState _req;
         private final long _start;
         
-        public CheckLeaseRequestStatus(RouterContext enclosingContext, LeaseRequestState state) {
-            super(enclosingContext);
-            _req = state;
+        public CheckLeaseRequestStatus() {
+            super(RequestLeaseSetJob.this.getContext());
             _start = System.currentTimeMillis();
-            getTiming().setStartAfter(state.getExpiration());
+            getTiming().setStartAfter(_requestState.getExpiration());
         }
         
         public void runJob() {
@@ -100,20 +101,22 @@ class RequestLeaseSetJob extends JobImpl {
                     _log.debug("Already dead, dont try to expire the leaseSet lookup");
                 return;
             }
-            if (_req.getIsSuccessful()) {
+            if (_requestState.getIsSuccessful()) {
                 // we didn't fail
-                RequestLeaseSetJob.CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetSuccess", 1, 0);
+                CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetSuccess", 1);
                 return;
             } else {
-                RequestLeaseSetJob.CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetTimeout", 1, 0);
+                CheckLeaseRequestStatus.this.getContext().statManager().addRateData("client.requestLeaseSetTimeout", 1);
                 if (_log.shouldLog(Log.ERROR)) {
                     long waited = System.currentTimeMillis() - _start;
-                    _log.error("Failed to receive a leaseSet in the time allotted (" + waited + "): " + _req + " for " 
+                    _log.error("Failed to receive a leaseSet in the time allotted (" + waited + "): " + _requestState + " 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());
+                if (_requestState.getOnFailed() != null)
+                    RequestLeaseSetJob.this.getContext().jobQueue().addJob(_requestState.getOnFailed());
+                _runner.failLeaseRequest(_requestState);
+                // Don't disconnect, the tunnel will retry
+                //_runner.disconnectClient("Took too long to request leaseSet");
             }
         }
         public String getName() { return "Check LeaseRequest Status"; }
diff --git a/router/java/src/net/i2p/router/peermanager/PeerManager.java b/router/java/src/net/i2p/router/peermanager/PeerManager.java
index 1cc82a46e5b257c4342c0cb20011bac08899cb85..bf3564298aac90d497023ba0c62a9cd35879e7a6 100644
--- a/router/java/src/net/i2p/router/peermanager/PeerManager.java
+++ b/router/java/src/net/i2p/router/peermanager/PeerManager.java
@@ -89,14 +89,16 @@ class PeerManager {
             super(_context.simpleTimer2(), REORGANIZE_TIME);
         }
         public void timeReached() {
+            long start = System.currentTimeMillis();
             try {
                 _organizer.reorganize(true);
             } catch (Throwable t) {
                 _log.log(Log.CRIT, "Error evaluating profiles", t);
             }
+            long orgtime = System.currentTimeMillis() - start;
             long uptime = _context.router().getUptime();
             long delay;
-            if (uptime > 2*60*60*1000)
+            if (orgtime > 1000 || uptime > 2*60*60*1000)
                 delay = REORGANIZE_TIME_LONG;
             else if (uptime > 10*60*1000)
                 delay = REORGANIZE_TIME_MEDIUM;