Author: toad
Date: 2009-01-22 18:32:20 +0000 (Thu, 22 Jan 2009)
New Revision: 25217

Added:
   branches/db4o/freenet/src/freenet/node/InsertTag.java
   branches/db4o/freenet/src/freenet/node/OfferReplyTag.java
   branches/db4o/freenet/src/freenet/node/RequestTag.java
   branches/db4o/freenet/src/freenet/node/UIDTag.java
Modified:
   branches/db4o/freenet/src/freenet/node/CHKInsertHandler.java
   branches/db4o/freenet/src/freenet/node/DarknetPeerNode.java
   branches/db4o/freenet/src/freenet/node/FailureTable.java
   branches/db4o/freenet/src/freenet/node/KeyTracker.java
   branches/db4o/freenet/src/freenet/node/Node.java
   branches/db4o/freenet/src/freenet/node/NodeClientCore.java
   branches/db4o/freenet/src/freenet/node/NodeCrypto.java
   branches/db4o/freenet/src/freenet/node/NodeDispatcher.java
   branches/db4o/freenet/src/freenet/node/NodeStarter.java
   branches/db4o/freenet/src/freenet/node/NodeStats.java
   branches/db4o/freenet/src/freenet/node/PacketSender.java
   branches/db4o/freenet/src/freenet/node/PeerManager.java
   branches/db4o/freenet/src/freenet/node/PeerMessageQueue.java
   branches/db4o/freenet/src/freenet/node/PeerNode.java
   branches/db4o/freenet/src/freenet/node/RequestHandler.java
   branches/db4o/freenet/src/freenet/node/RequestSender.java
   branches/db4o/freenet/src/freenet/node/SSKInsertHandler.java
   branches/db4o/freenet/src/freenet/node/SessionKey.java
   branches/db4o/freenet/src/freenet/node/Version.java
   
branches/db4o/freenet/src/freenet/node/simulator/RealNodeNetworkColoringTest.java
   branches/db4o/freenet/src/freenet/node/useralerts/UserAlertManager.java
Log:
Merge trunk up to 25205 (1203) into db4o.


Modified: branches/db4o/freenet/src/freenet/node/CHKInsertHandler.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/CHKInsertHandler.java        
2009-01-22 18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/CHKInsertHandler.java        
2009-01-22 18:32:20 UTC (rev 25217)
@@ -45,14 +45,16 @@
     private BlockReceiver br;
     private Thread runThread;
     PartiallyReceivedBlock prb;
+    final InsertTag tag;
     private static boolean logMINOR;

-    CHKInsertHandler(Message req, PeerNode source, long id, Node node, long 
startTime) {
+    CHKInsertHandler(Message req, PeerNode source, long id, Node node, long 
startTime, InsertTag tag) {
         this.req = req;
         this.node = node;
         this.uid = id;
         this.source = source;
         this.startTime = startTime;
+        this.tag = tag;
         key = (NodeCHK) req.getObject(DMT.FREENET_ROUTING_KEY);
         htl = req.getShort(DMT.HTL);
         if(htl <= 0) htl = 1;
@@ -71,11 +73,13 @@
                realRun();
                } catch (OutOfMemoryError e) {
                        OOMHandler.handleOOM(e);
+                       tag.handlerThrew(e);
         } catch (Throwable t) {
             Logger.error(this, "Caught in run() "+t, t);
+            tag.handlerThrew(t);
         } finally {
                if(logMINOR) Logger.minor(this, "Exiting CHKInsertHandler.run() 
for "+uid);
-            node.unlockUID(uid, false, true, false, false, false);
+            node.unlockUID(uid, false, true, false, false, false, tag);
         }
     }

@@ -117,7 +121,7 @@
                        Message m = DMT.createFNPInsertTransfersCompleted(uid, 
true);
                        source.sendAsync(m, null, this);
                        prb = new PartiallyReceivedBlock(Node.PACKETS_IN_BLOCK, 
Node.PACKET_SIZE);
-                       br = new BlockReceiver(node.usm, source, uid, prb, 
this);
+                       br = new BlockReceiver(node.usm, source, uid, prb, 
this, node.getTicker(), false);
                        prb.abort(RetrievalException.NO_DATAINSERT, "No 
DataInsert");
                        br.sendAborted(RetrievalException.NO_DATAINSERT, "No 
DataInsert");
                        return;
@@ -139,7 +143,7 @@
         prb = new PartiallyReceivedBlock(Node.PACKETS_IN_BLOCK, 
Node.PACKET_SIZE);
         if(htl > 0)
             sender = node.makeInsertSender(key, htl, uid, source, headers, 
prb, false, true);
-        br = new BlockReceiver(node.usm, source, uid, prb, this);
+        br = new BlockReceiver(node.usm, source, uid, prb, this, 
node.getTicker(), false);

         // Receive the data, off thread
         Runnable dataReceiver = new DataReceiver();
@@ -434,7 +438,7 @@
                                else
                                        // Annoying, but we have stats for 
this; no need to call attention to it, it's unlikely to be a bug.
                                        Logger.normal(this, "Failed to retrieve 
("+e.getReason()+"/"+RetrievalException.getErrString(e.getReason())+"): "+e, e);
-               node.nodeStats.failedBlockReceive();
+               node.nodeStats.failedBlockReceive(false, false, false);
                 return;
             } catch (Throwable t) {
                 Logger.error(this, "Caught "+t, t);

Modified: branches/db4o/freenet/src/freenet/node/DarknetPeerNode.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/DarknetPeerNode.java 2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/DarknetPeerNode.java 2009-01-22 
18:32:20 UTC (rev 25217)
@@ -364,7 +364,7 @@
                for (File extraPeerDataFile : extraPeerDataFiles) {
                        Integer fileNumber;
                        try {
-                               fileNumber = new 
Integer(extraPeerDataFile.getName());
+                               fileNumber = 
Integer.valueOf(extraPeerDataFile.getName());
                        } catch (NumberFormatException e) {
                                gotError = true;
                                continue;
@@ -532,7 +532,7 @@
                                
synchronized(queuedToSendN2NMExtraPeerDataFileNumbers) {
                                        fs.putOverwrite("extraPeerDataType", 
Integer.toString(extraPeerDataType));
                                        fs.removeValue("sentTime");
-                                       
queuedToSendN2NMExtraPeerDataFileNumbers.add(new Integer(fileNumber));
+                                       
queuedToSendN2NMExtraPeerDataFileNumbers.add(Integer.valueOf(fileNumber));
                                }
                        }
                        return true;

Modified: branches/db4o/freenet/src/freenet/node/FailureTable.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/FailureTable.java    2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/FailureTable.java    2009-01-22 
18:32:20 UTC (rev 25217)
@@ -380,16 +380,16 @@
         * @param source The node that asked for the key.
         * @throws NotConnectedException If the sender ceases to be connected.
         */
-       public void sendOfferedKey(final Key key, final boolean isSSK, final 
boolean needPubKey, final long uid, final PeerNode source) throws 
NotConnectedException {
+       public void sendOfferedKey(final Key key, final boolean isSSK, final 
boolean needPubKey, final long uid, final PeerNode source, final OfferReplyTag 
tag) throws NotConnectedException {
                this.offerExecutor.execute(new Runnable() {
                        public void run() {
                                try {
-                                       innerSendOfferedKey(key, isSSK, 
needPubKey, uid, source);
+                                       innerSendOfferedKey(key, isSSK, 
needPubKey, uid, source, tag);
                                } catch (NotConnectedException e) {
-                                       node.unlockUID(uid, isSSK, false, 
false, true, false);
+                                       node.unlockUID(uid, isSSK, false, 
false, true, false, tag);
                                        // Too bad.
                                } catch (Throwable t) {
-                                       node.unlockUID(uid, isSSK, false, 
false, true, false);
+                                       node.unlockUID(uid, isSSK, false, 
false, true, false, tag);
                                        Logger.error(this, "Caught "+t+" 
sending offered key");
                                }
                        }
@@ -401,13 +401,13 @@
         * on a separate thread. However, blocking disk I/O *should happen on 
this thread*. We deliberately
         * serialise it, as high latencies can otherwise result.
         */
-       protected void innerSendOfferedKey(Key key, final boolean isSSK, 
boolean needPubKey, final long uid, final PeerNode source) throws 
NotConnectedException {
+       protected void innerSendOfferedKey(Key key, final boolean isSSK, 
boolean needPubKey, final long uid, final PeerNode source, final OfferReplyTag 
tag) throws NotConnectedException {
                if(isSSK) {
                        SSKBlock block = node.fetch((NodeSSK)key, false);
                        if(block == null) {
                                // Don't have the key
                                
source.sendAsync(DMT.createFNPGetOfferedKeyInvalid(uid, 
DMT.GET_OFFERED_KEY_REJECTED_NO_KEY), null, senderCounter);
-                               node.unlockUID(uid, isSSK, false, false, true, 
false);
+                               node.unlockUID(uid, isSSK, false, false, true, 
false, tag);
                                return;
                        }

@@ -434,7 +434,7 @@
                                        } catch (SyncSendWaitedTooLongException 
e) {
                                                // Impossible
                                        } finally {
-                                               node.unlockUID(uid, isSSK, 
false, false, true, false);
+                                               node.unlockUID(uid, isSSK, 
false, false, true, false, tag);
                                        }
                                }

@@ -453,7 +453,7 @@
                        if(block == null) {
                                // Don't have the key
                                
source.sendAsync(DMT.createFNPGetOfferedKeyInvalid(uid, 
DMT.GET_OFFERED_KEY_REJECTED_NO_KEY), null, senderCounter);
-                               node.unlockUID(uid, isSSK, false, false, true, 
false);
+                               node.unlockUID(uid, isSSK, false, false, true, 
false, tag);
                                return;
                        }
                        Message df = DMT.createFNPCHKDataFound(uid, 
block.getRawHeaders());
@@ -474,7 +474,7 @@
                                        } catch (Throwable t) {
                                                Logger.error(this, "Sending 
offered key failed: "+t, t);
                                        } finally {
-                                               node.unlockUID(uid, isSSK, 
false, false, true, false);
+                                               node.unlockUID(uid, isSSK, 
false, false, true, false, tag);
                                        }
                                }


Copied: branches/db4o/freenet/src/freenet/node/InsertTag.java (from rev 25205, 
trunk/freenet/src/freenet/node/InsertTag.java)
===================================================================
--- branches/db4o/freenet/src/freenet/node/InsertTag.java                       
        (rev 0)
+++ branches/db4o/freenet/src/freenet/node/InsertTag.java       2009-01-22 
18:32:20 UTC (rev 25217)
@@ -0,0 +1,45 @@
+package freenet.node;
+
+import freenet.support.Logger;
+import freenet.support.TimeUtil;
+
+/**
+ * Represents an insert.
+ * @author Matthew Toseland <toad at amphibian.dyndns.org> (0xE43DA450)
+ */
+public class InsertTag extends UIDTag {
+       
+       final boolean ssk;
+       
+       enum START {
+               LOCAL,
+               REMOTE
+       }
+       
+       START start;
+       private Throwable handlerThrew;
+       
+       InsertTag(boolean ssk, START start) {
+               super();
+               this.start = start;
+               this.ssk = ssk;
+       }
+
+       public void handlerThrew(Throwable t) {
+               handlerThrew = t;
+       }
+
+       @Override
+       public void logStillPresent(Long uid) {
+               StringBuffer sb = new StringBuffer();
+               sb.append("Still present after 
").append(TimeUtil.formatTime(age()));
+               sb.append(" : ").append(uid).append(" : start=").append(start);
+               sb.append(" ssk=").append(ssk);
+               sb.append(" thrown=").append(handlerThrew);
+               if(handlerThrew != null)
+                       Logger.error(this, sb.toString(), handlerThrew);
+               else
+                       Logger.error(this, sb.toString());
+       }
+
+}

Modified: branches/db4o/freenet/src/freenet/node/KeyTracker.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/KeyTracker.java      2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/KeyTracker.java      2009-01-22 
18:32:20 UTC (rev 25217)
@@ -18,7 +18,6 @@
         * without changing the session key. */
        final PacketTracker packets;

-       private static boolean logMINOR;
        /** Parent PeerNode */
        public final PeerNode pn;
        /** Cipher to both encrypt outgoing packets with and decrypt

Modified: branches/db4o/freenet/src/freenet/node/Node.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/Node.java    2009-01-22 18:08:25 UTC 
(rev 25216)
+++ branches/db4o/freenet/src/freenet/node/Node.java    2009-01-22 18:32:20 UTC 
(rev 25217)
@@ -66,7 +66,9 @@
 import freenet.io.comm.Peer;
 import freenet.io.comm.PeerParseException;
 import freenet.io.comm.ReferenceSignatureVerificationException;
+import freenet.io.comm.RetrievalException;
 import freenet.io.comm.UdpSocketHandler;
+import freenet.io.xfer.BlockReceiver;
 import freenet.io.xfer.PartiallyReceivedBlock;
 import freenet.keys.CHKBlock;
 import freenet.keys.CHKVerifyException;
@@ -380,17 +382,17 @@
        public boolean disableHangCheckers;

        /** HashSet of currently running request UIDs */
-       private final HashSet<Long> runningUIDs;
-       private final HashSet<Long> runningCHKGetUIDs;
-       private final HashSet<Long> runningLocalCHKGetUIDs;
-       private final HashSet<Long> runningSSKGetUIDs;
-       private final HashSet<Long> runningLocalSSKGetUIDs;
-       private final HashSet<Long> runningCHKPutUIDs;
-       private final HashSet<Long> runningLocalCHKPutUIDs;
-       private final HashSet<Long> runningSSKPutUIDs;
-       private final HashSet<Long> runningLocalSSKPutUIDs;
-       private final HashSet<Long> runningCHKOfferReplyUIDs;
-       private final HashSet<Long> runningSSKOfferReplyUIDs;
+       private final HashMap<Long,UIDTag> runningUIDs;
+       private final HashMap<Long,RequestTag> runningCHKGetUIDs;
+       private final HashMap<Long,RequestTag> runningLocalCHKGetUIDs;
+       private final HashMap<Long,RequestTag> runningSSKGetUIDs;
+       private final HashMap<Long,RequestTag> runningLocalSSKGetUIDs;
+       private final HashMap<Long,InsertTag> runningCHKPutUIDs;
+       private final HashMap<Long,InsertTag> runningLocalCHKPutUIDs;
+       private final HashMap<Long,InsertTag> runningSSKPutUIDs;
+       private final HashMap<Long,InsertTag> runningLocalSSKPutUIDs;
+       private final HashMap<Long,OfferReplyTag> runningCHKOfferReplyUIDs;
+       private final HashMap<Long,OfferReplyTag> runningSSKOfferReplyUIDs;

        /** Semi-unique ID for swap requests. Used to identify us so that the
         * topology can be reconstructed. */
@@ -763,17 +765,17 @@
                transferringRequestSenders = new HashMap<NodeCHK, 
RequestSender>();
                transferringRequestHandlers = new HashSet<Long>();
                insertSenders = new HashMap<KeyHTLPair, AnyInsertSender>();
-               runningUIDs = new HashSet<Long>();
-               runningCHKGetUIDs = new HashSet<Long>();
-               runningLocalCHKGetUIDs = new HashSet<Long>();
-               runningSSKGetUIDs = new HashSet<Long>();
-               runningLocalSSKGetUIDs = new HashSet<Long>();
-               runningCHKPutUIDs = new HashSet<Long>();
-               runningLocalCHKPutUIDs = new HashSet<Long>();
-               runningSSKPutUIDs = new HashSet<Long>();
-               runningLocalSSKPutUIDs = new HashSet<Long>();
-               runningCHKOfferReplyUIDs = new HashSet<Long>();
-               runningSSKOfferReplyUIDs = new HashSet<Long>();
+               runningUIDs = new HashMap<Long,UIDTag>();
+               runningCHKGetUIDs = new HashMap<Long,RequestTag>();
+               runningLocalCHKGetUIDs = new HashMap<Long,RequestTag>();
+               runningSSKGetUIDs = new HashMap<Long,RequestTag>();
+               runningLocalSSKGetUIDs = new HashMap<Long,RequestTag>();
+               runningCHKPutUIDs = new HashMap<Long,InsertTag>();
+               runningLocalCHKPutUIDs = new HashMap<Long,InsertTag>();
+               runningSSKPutUIDs = new HashMap<Long,InsertTag>();
+               runningLocalSSKPutUIDs = new HashMap<Long,InsertTag>();
+               runningCHKOfferReplyUIDs = new HashMap<Long,OfferReplyTag>();
+               runningSSKOfferReplyUIDs = new HashMap<Long,OfferReplyTag>();

                this.securityLevels = new SecurityLevels(this, config);

@@ -2280,6 +2282,8 @@

                this.clientCore.start(config);

+               startDeadUIDChecker();
+               
                // After everything has been created, write the config file 
back to disk.
                if(config instanceof FreenetFilePersistentConfig) {
                        FreenetFilePersistentConfig cfg = 
(FreenetFilePersistentConfig) config;
@@ -2953,55 +2957,144 @@
                return is;
        }

-       public boolean lockUID(long uid, boolean ssk, boolean insert, boolean 
offerReply, boolean local) {
+       public boolean lockUID(long uid, boolean ssk, boolean insert, boolean 
offerReply, boolean local, UIDTag tag) {
                synchronized(runningUIDs) {
-                       if(!runningUIDs.add(uid)) {
-                               // Already present.
-                               return false;
-                       }
+                       if(runningUIDs.containsKey(uid)) return false; // 
Already present.
+                       runningUIDs.put(uid, tag);
                }
                // If these are switched around, we must remember to remove 
from both.
-               HashSet<Long> set = getUIDTracker(ssk, insert, offerReply, 
local);
-               synchronized(set) {
-                       if(logMINOR) Logger.minor(this, "Locking "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+" local="+local+" 
size="+set.size());
-                       set.add(uid);
-                       if(logMINOR) Logger.minor(this, "Locked "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+" local="+local+" 
size="+set.size());
+               if(offerReply) {
+                       HashMap<Long,OfferReplyTag> map = getOfferTracker(ssk);
+                       innerLock(map, (OfferReplyTag)tag, uid, ssk, insert, 
offerReply, local);
+               } else if(insert) {
+                       HashMap<Long,InsertTag> map = 
getInsertTracker(ssk,local);
+                       innerLock(map, (InsertTag)tag, uid, ssk, insert, 
offerReply, local);
+               } else {
+                       HashMap<Long,RequestTag> map = 
getRequestTracker(ssk,local);
+                       innerLock(map, (RequestTag)tag, uid, ssk, insert, 
offerReply, local);
                }
                return true;
        }

-       public void unlockUID(long uid, boolean ssk, boolean insert, boolean 
canFail, boolean offerReply, boolean local) {
+       private<T extends UIDTag> void innerLock(HashMap<Long, T> map, T tag, 
Long uid, boolean ssk, boolean insert, boolean offerReply, boolean local) {
+               synchronized(map) {
+                       if(logMINOR) Logger.minor(this, "Locking "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+" local="+local+" 
size="+map.size(), new Exception("debug"));
+                       if(map.containsKey(uid)) {
+                               Logger.error(this, "Already have UID in 
specific map ("+ssk+","+insert+","+offerReply+","+local+") but not in general 
map: trying to register "+tag+" but already have "+map.get(uid));
+                       }
+                       map.put(uid, tag);
+                       if(logMINOR) Logger.minor(this, "Locked "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+" local="+local+" 
size="+map.size());
+               }
+       }
+
+       public void unlockUID(long uid, boolean ssk, boolean insert, boolean 
canFail, boolean offerReply, boolean local, UIDTag tag) {
                completed(uid);
-               HashSet<Long> set = getUIDTracker(ssk, insert, offerReply, 
local);
-               synchronized(set) {
-                       if(logMINOR) Logger.minor(this, "Unlocking "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+", local="+local+" 
size="+set.size());
-                       set.remove(uid);
-                       if(logMINOR) Logger.minor(this, "Unlocked "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+", local="+local+" 
size="+set.size());
+               
+               if(offerReply) {
+                       HashMap<Long,OfferReplyTag> map = getOfferTracker(ssk);
+                       innerUnlock(map, (OfferReplyTag)tag, uid, ssk, insert, 
offerReply, local, canFail);
+               } else if(insert) {
+                       HashMap<Long,InsertTag> map = 
getInsertTracker(ssk,local);
+                       innerUnlock(map, (InsertTag)tag, uid, ssk, insert, 
offerReply, local, canFail);
+               } else {
+                       HashMap<Long,RequestTag> map = 
getRequestTracker(ssk,local);
+                       innerUnlock(map, (RequestTag)tag, uid, ssk, insert, 
offerReply, local, canFail);
                }
+               
                synchronized(runningUIDs) {
-                       if(!runningUIDs.remove(uid) && !canFail)
-                               throw new IllegalStateException("Could not 
unlock "+uid+ '!');
+                       UIDTag oldTag = runningUIDs.get(uid);
+                       if(oldTag == null) {
+                               if(canFail) return;
+                               throw new IllegalStateException("Could not 
unlock "+uid+ "! : ssk="+ssk+" insert="+insert+" canFail="+canFail+" 
offerReply="+offerReply+" local="+local);
+                       } else if(tag != oldTag) {
+                               if(canFail) return;
+                               Logger.error(this, "Removing "+tag+" for 
"+uid+" but "+tag+" is registered!");
+                               return;
+                       } else {
+                               runningUIDs.remove(uid);
+                       }
                }
        }

-       private HashSet<Long> getUIDTracker(boolean ssk, boolean insert, 
boolean offerReply, boolean local) {
+       private<T extends UIDTag> void innerUnlock(HashMap<Long, T> map, T tag, 
Long uid, boolean ssk, boolean insert, boolean offerReply, boolean local, 
boolean canFail) {
+               synchronized(map) {
+                       if(logMINOR) Logger.minor(this, "Unlocking "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+" local="+local+" 
size="+map.size(), new Exception("debug"));
+                       if(map.get(uid) != tag) {
+                               if(canFail) {
+                                       if(logMINOR) Logger.minor(this, "Can 
fail and did fail: removing "+tag+" got "+map.get(uid)+" for "+uid);
+                               } else {
+                                       Logger.error(this, "Removing "+tag+" 
for "+uid+" returned "+map.get(uid));
+                               }
+                       } else
+                               map.remove(uid);
+                       if(logMINOR) Logger.minor(this, "Unlocked "+uid+" 
ssk="+ssk+" insert="+insert+" offerReply="+offerReply+" local="+local+" 
size="+map.size());
+               }
+       }
+
+       private HashMap<Long, RequestTag> getRequestTracker(boolean ssk, 
boolean local) {
                if(ssk) {
-                       if(offerReply)
-                               return runningSSKOfferReplyUIDs;
-                       if(!local)
-                               return insert ? runningSSKPutUIDs : 
runningSSKGetUIDs;
-                       else
-                               return insert ? runningLocalSSKPutUIDs : 
runningLocalSSKGetUIDs;
+                       return local ? runningLocalSSKGetUIDs : 
runningSSKGetUIDs;
                } else {
-                       if(offerReply)
-                               return runningCHKOfferReplyUIDs;
-                       if(!local)
-                               return insert ? runningCHKPutUIDs : 
runningCHKGetUIDs;
-                       else
-                               return insert ? runningLocalCHKPutUIDs : 
runningLocalCHKGetUIDs;
+                       return local ? runningLocalCHKGetUIDs : 
runningCHKGetUIDs;
                }
        }
+
+       private HashMap<Long, InsertTag> getInsertTracker(boolean ssk, boolean 
local) {
+               if(ssk) {
+                       return local ? runningLocalSSKPutUIDs : 
runningSSKPutUIDs;
+               } else {
+                       return local ? runningLocalCHKPutUIDs : 
runningCHKPutUIDs;
+               }
+       }
+
+       private HashMap<Long, OfferReplyTag> getOfferTracker(boolean ssk) {
+               return ssk ? runningSSKOfferReplyUIDs : 
runningCHKOfferReplyUIDs;
+       }
+
+       static final int TIMEOUT = 10 * 60 * 1000;

+       private void startDeadUIDChecker() {
+               getTicker().queueTimedJob(deadUIDChecker, TIMEOUT);
+       }
+
+       private Runnable deadUIDChecker = new Runnable() {
+               public void run() {
+                       try {
+                               checkUIDs(runningLocalSSKGetUIDs);
+                               checkUIDs(runningLocalCHKGetUIDs);
+                               checkUIDs(runningLocalSSKPutUIDs);
+                               checkUIDs(runningLocalCHKPutUIDs);
+                               checkUIDs(runningSSKGetUIDs);
+                               checkUIDs(runningCHKGetUIDs);
+                               checkUIDs(runningSSKPutUIDs);
+                               checkUIDs(runningCHKPutUIDs);
+                               checkUIDs(runningSSKOfferReplyUIDs);
+                               checkUIDs(runningCHKOfferReplyUIDs);
+                       } finally {
+                               getTicker().queueTimedJob(this, 60*1000);
+                       }
+               }
+
+               private void checkUIDs(HashMap<Long, ? extends UIDTag> map) {
+                       Long[] uids;
+                       UIDTag[] tags;
+                       synchronized(map) {
+                               uids = map.keySet().toArray(new 
Long[map.size()]);
+                               tags = map.values().toArray(new 
UIDTag[map.size()]);
+                       }
+                       long now = System.currentTimeMillis();
+                       for(int i=0;i<uids.length;i++) {
+                               if(now - tags[i].createdTime > TIMEOUT) {
+                                       tags[i].logStillPresent(uids[i]);
+                                       synchronized(map) {
+                                               map.remove(uids[i]);
+                                       }
+                               }
+                       }
+               }
+       };
+       
+       
        /**
         * @return Some status information.
         */
@@ -3927,7 +4020,7 @@

        public void addRunningUIDs(Vector<Long> list) {
                synchronized(runningUIDs) {
-                       list.addAll(runningUIDs);
+                       list.addAll(runningUIDs.keySet());
                }
        }

@@ -3981,4 +4074,138 @@
                return false;
        }

+       private volatile long turtleCount;
+       
+       /**
+        * Make a running request sender into a turtle request.
+        * Backoff: when the transfer finishes, or after 10 seconds if no 
cancellation.
+        * Downstream: Cancel all dependant RequestHandler's and local requests.
+        * This also removes it from the load management code.
+        * Registration: We track the turtles for each peer, and overall. No 
two turtles from the
+        * same node may share the same key, and there is an overall limit.
+        * @param sender
+        */
+       public void makeTurtle(RequestSender sender) {
+               // Registration
+               // FIXME check the datastore.
+               if(!this.registerTurtleTransfer(sender)) {
+                       // Too many turtles running, or already two turtles for 
this key (we allow two in case one peer turtles as a DoS).
+                       sender.killTurtle();
+                       Logger.error(this, "Didn't make turtle (global) for key 
"+sender.key+" for "+sender);
+                       return;
+               }
+               PeerNode from = sender.transferringFrom();
+               if(!from.registerTurtleTransfer(sender)) {
+                       // Too many turtles running, or already a turtle for 
this key.
+                       // Abort it.
+                       unregisterTurtleTransfer(sender);
+                       sender.killTurtle();
+                       Logger.error(this, "Didn't make turtle (peer) for key 
"+sender.key+" for "+sender);
+                       return;
+               }
+               Logger.error(this, "TURTLING: "+sender.key+" for "+sender);
+               // Do not transfer coalesce!!
+               synchronized(transferringRequestSenders) {
+                       transferringRequestSenders.remove((NodeCHK)sender.key);
+               }
+               turtleCount++;
+               
+               // Abort downstream transfers, set the turtle mode flag and set 
up the backoff callback.
+               sender.setTurtle();
+       }
+
+       public long getTurtleCount() {
+               return turtleCount;
+       }
+       
+       private static int MAX_TURTLES = 10;
+       private static int MAX_TURTLES_PER_KEY = 2;
+       
+       private HashMap<Key,RequestSender[]> turtlingTransfers = new 
HashMap<Key,RequestSender[]>();
+       
+       private boolean registerTurtleTransfer(RequestSender sender) {
+               Key key = sender.key;
+               synchronized(turtlingTransfers) {
+                       if(getNumIncomingTurtles() >= MAX_TURTLES) {
+                               Logger.error(this, "Too many turtles running 
globally");
+                               return false;
+                       }
+                       if(!turtlingTransfers.containsKey(key)) {
+                               turtlingTransfers.put(key, new RequestSender[] 
{ sender });
+                               Logger.error(this, "Running turtles (a): 
"+getNumIncomingTurtles()+" : "+turtlingTransfers.size());
+                               return true;
+                       } else {
+                               RequestSender[] senders = 
turtlingTransfers.get(key);
+                               if(senders.length >= MAX_TURTLES_PER_KEY) {
+                                       Logger.error(this, "Too many turtles 
for key globally");
+                                       return false;
+                               }
+                               for(int i=0;i<senders.length;i++) {
+                                       if(senders[i] == sender) {
+                                               Logger.error(this, "Registering 
turtle for "+sender+" : "+key+" twice! (globally)");
+                                               return false;
+                                       }
+                               }
+                               RequestSender[] newSenders = new 
RequestSender[senders.length+1];
+                               System.arraycopy(senders, 0, newSenders, 0, 
senders.length);
+                               newSenders[senders.length] = sender;
+                               turtlingTransfers.put(key, newSenders);
+                               Logger.error(this, "Running turtles (b): 
"+getNumIncomingTurtles()+" : "+turtlingTransfers.size());
+                               return true;
+                       }
+               }
+       }
+       
+       public void unregisterTurtleTransfer(RequestSender sender) {
+               Key key = sender.key;
+               synchronized(turtlingTransfers) {
+                       if(!turtlingTransfers.containsKey(key)) {
+                               Logger.error(this, "Removing turtle "+sender+" 
for "+key+" : DOES NOT EXIST IN GLOBAL TURTLES LIST");
+                               return;
+                       }
+                       RequestSender[] senders = turtlingTransfers.get(key);
+                       if(senders.length == 1 && senders[0] == sender) {
+                               turtlingTransfers.remove(key);
+                               return;
+                       }
+                       if(senders.length == 2) {
+                               if(senders[0] == sender) {
+                                       turtlingTransfers.put(key, new 
RequestSender[] { senders[1] });
+                               } else if(senders[1] == sender) {
+                                       turtlingTransfers.put(key, new 
RequestSender[] { senders[0] });
+                               }
+                               return;
+                       }
+                       int x = 0;
+                       for(int i=0;i<senders.length;i++) {
+                               if(senders[i] == sender) x++;
+                       }
+                       if(x == 0) {
+                               Logger.error(this, "Turtle not in global 
register: "+sender+" for "+key);
+                               return;
+                       }
+                       if(senders.length == x) {
+                               Logger.error(this, "Lots of copies of turtle: 
"+x);
+                               turtlingTransfers.remove(key);
+                               return;
+                       }
+                       RequestSender[] newSenders = new 
RequestSender[senders.length - x];
+                       int idx = 0;
+                       for(RequestSender s : senders) {
+                               if(s == sender) continue;
+                               newSenders[idx++] = s;
+                       }
+                       turtlingTransfers.put(key, newSenders);
+               }
+       }
+
+       public int getNumIncomingTurtles() {
+               synchronized(turtlingTransfers) {
+                       int turtles = 0;
+                       for(RequestSender[] senders : 
turtlingTransfers.values())
+                               turtles += senders.length;
+                       return turtles;
+               }
+       }
+
 }

Modified: branches/db4o/freenet/src/freenet/node/NodeClientCore.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/NodeClientCore.java  2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/NodeClientCore.java  2009-01-22 
18:32:20 UTC (rev 25217)
@@ -617,11 +617,12 @@
        public void asyncGet(Key key, boolean cache, boolean offersOnly, final 
SimpleRequestSenderCompletionListener listener) {
                final long uid = random.nextLong();
                final boolean isSSK = key instanceof NodeSSK;
-               if(!node.lockUID(uid, isSSK, false, false, true)) {
+               final RequestTag tag = new RequestTag(isSSK, 
RequestTag.START.ASYNC_GET);
+               if(!node.lockUID(uid, isSSK, false, false, true, tag)) {
                        Logger.error(this, "Could not lock UID just randomly 
generated: " + uid + " - probably indicates broken PRNG");
                        return;
                }
-               asyncGet(key, cache, offersOnly, uid, new 
RequestSender.Listener() {
+               asyncGet(key, isSSK, cache, offersOnly, uid, new 
RequestSender.Listener() {

                        public void onCHKTransferBegins() {
                                // Ignore
@@ -633,11 +634,16 @@

                        public void onRequestSenderFinished(int status) {
                                // If transfer coalescing has happened, we may 
have already unlocked.
-                               node.unlockUID(uid, isSSK, false, true, false, 
true);
+                               node.unlockUID(uid, isSSK, false, true, false, 
true, tag);
+                               tag.setRequestSenderFinished(status);
                                if(listener != null)
                                        listener.completed(status == 
RequestSender.SUCCESS);
                        }
-               });
+
+                       public void onAbortDownstreamTransfers(int reason, 
String desc) {
+                               // Ignore, onRequestSenderFinished will also be 
called.
+                       }
+               }, tag);
        }

        /**
@@ -646,26 +652,28 @@
         * anything and will run asynchronously. Caller is responsible for 
unlocking the UID.
         * @param key
         */
-       void asyncGet(Key key, boolean cache, boolean offersOnly, long uid, 
RequestSender.Listener listener) {
+       void asyncGet(Key key, boolean isSSK, boolean cache, boolean 
offersOnly, long uid, RequestSender.Listener listener, RequestTag tag) {
                try {
                        Object o = node.makeRequestSender(key, node.maxHTL(), 
uid, null, false, cache, false, offersOnly);
                        if(o instanceof KeyBlock) {
-                               node.unlockUID(uid, false, false, true, false, 
true);
+                               tag.servedFromDatastore = true;
+                               node.unlockUID(uid, isSSK, false, true, false, 
true, tag);
                                return; // Already have it.
                        }
                        RequestSender rs = (RequestSender) o;
+                       tag.setSender(rs);
                        rs.addListener(listener);
                        if(rs.uid != uid)
-                               node.unlockUID(uid, false, false, false, false, 
true);
+                               node.unlockUID(uid, isSSK, false, false, false, 
true, tag);
                        // Else it has started a request.
                        if(logMINOR)
                                Logger.minor(this, "Started " + o + " for " + 
uid + " for " + key);
                } catch(RuntimeException e) {
                        Logger.error(this, "Caught error trying to start 
request: " + e, e);
-                       node.unlockUID(uid, false, false, true, false, true);
+                       node.unlockUID(uid, isSSK, false, true, false, true, 
tag);
                } catch(Error e) {
                        Logger.error(this, "Caught error trying to start 
request: " + e, e);
-                       node.unlockUID(uid, false, false, true, false, true);
+                       node.unlockUID(uid, isSSK, false, true, false, true, 
tag);
                }
        }

@@ -682,7 +690,8 @@
                logMINOR = Logger.shouldLog(Logger.MINOR, this);
                long startTime = System.currentTimeMillis();
                long uid = random.nextLong();
-               if(!node.lockUID(uid, false, false, false, true)) {
+               RequestTag tag = new RequestTag(false, RequestTag.START.LOCAL);
+               if(!node.lockUID(uid, false, false, false, true, tag)) {
                        Logger.error(this, "Could not lock UID just randomly 
generated: " + uid + " - probably indicates broken PRNG");
                        throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
                }
@@ -690,6 +699,7 @@
                        Object o = node.makeRequestSender(key.getNodeCHK(), 
node.maxHTL(), uid, null, localOnly, cache, ignoreStore, false);
                        if(o instanceof CHKBlock)
                                try {
+                                       tag.setServedFromDatastore();
                                        return new ClientCHKBlock((CHKBlock) o, 
key);
                                } catch(CHKVerifyException e) {
                                        Logger.error(this, "Does not verify: " 
+ e, e);
@@ -709,6 +719,9 @@
                                }

                                int status = rs.getStatus();
+                               
+                               if(rs.abortedDownstreamTransfers())
+                                       status = RequestSender.TRANSFER_FAILED;

                                if(status == RequestSender.NOT_FINISHED)
                                        continue;
@@ -730,6 +743,8 @@
                                                // See below
                                                
requestStarters.rejectedOverload(false, false);
                                                rejectedOverload = true;
+                                               long rtt = 
System.currentTimeMillis() - startTime;
+                                               
node.nodeStats.reportCHKTime(rtt, false);
                                        }
                                } else
                                        if(rs.hasForwarded() &&
@@ -744,9 +759,13 @@
                                                        
requestStarters.requestCompleted(false, false, key.getNodeKey());
                                                // Count towards RTT even if 
got a RejectedOverload - but not if timed out.
                                                
requestStarters.chkRequestThrottle.successfulCompletion(rtt);
+                                               
node.nodeStats.reportCHKTime(rtt, status == RequestSender.SUCCESS);
+                                               if(status == 
RequestSender.SUCCESS) {
+                                                       Logger.minor(this, 
"Successful CHK fetch took "+rtt);
+                                               }
                                        }

-                               if(rs.getStatus() == RequestSender.SUCCESS)
+                               if(status == RequestSender.SUCCESS)
                                        try {
                                                return new 
ClientCHKBlock(rs.getPRB().getBlock(), rs.getHeaders(), key, true);
                                        } catch(CHKVerifyException e) {
@@ -757,8 +776,7 @@
                                                throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
                                        }
                                else {
-                                       int rStatus = rs.getStatus();
-                                       switch(rStatus) {
+                                       switch(status) {
                                                case RequestSender.NOT_FINISHED:
                                                        Logger.error(this, "RS 
still running in getCHK!: " + rs);
                                                        throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
@@ -780,13 +798,13 @@
                                                case 
RequestSender.INTERNAL_ERROR:
                                                        throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
                                                default:
-                                                       Logger.error(this, 
"Unknown RequestSender code in getCHK: " + rStatus + " on " + rs);
+                                                       Logger.error(this, 
"Unknown RequestSender code in getCHK: " + status + " on " + rs);
                                                        throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
                                        }
                                }
                        }
                } finally {
-                       node.unlockUID(uid, false, false, true, false, true);
+                       node.unlockUID(uid, false, false, true, false, true, 
tag);
                }
        }

@@ -794,7 +812,8 @@
                logMINOR = Logger.shouldLog(Logger.MINOR, this);
                long startTime = System.currentTimeMillis();
                long uid = random.nextLong();
-               if(!node.lockUID(uid, true, false, false, true)) {
+               RequestTag tag = new RequestTag(true, RequestTag.START.LOCAL);
+               if(!node.lockUID(uid, true, false, false, true, tag)) {
                        Logger.error(this, "Could not lock UID just randomly 
generated: " + uid + " - probably indicates broken PRNG");
                        throw new 
LowLevelGetException(LowLevelGetException.INTERNAL_ERROR);
                }
@@ -802,6 +821,7 @@
                        Object o = node.makeRequestSender(key.getNodeKey(), 
node.maxHTL(), uid, null, localOnly, cache, ignoreStore, false);
                        if(o instanceof SSKBlock)
                                try {
+                                       tag.setServedFromDatastore();
                                        SSKBlock block = (SSKBlock) o;
                                        key.setPublicKey(block.getPubKey());
                                        return ClientSSKBlock.construct(block, 
key);
@@ -897,7 +917,7 @@
                                        }
                        }
                } finally {
-                       node.unlockUID(uid, true, false, true, false, true);
+                       node.unlockUID(uid, true, false, true, false, true, 
tag);
                }
        }

@@ -917,7 +937,8 @@
                PartiallyReceivedBlock prb = new 
PartiallyReceivedBlock(Node.PACKETS_IN_BLOCK, Node.PACKET_SIZE, data);
                CHKInsertSender is;
                long uid = random.nextLong();
-               if(!node.lockUID(uid, false, true, false, true)) {
+               InsertTag tag = new InsertTag(false, InsertTag.START.LOCAL);
+               if(!node.lockUID(uid, false, true, false, true, tag)) {
                        Logger.error(this, "Could not lock UID just randomly 
generated: " + uid + " - probably indicates broken PRNG");
                        throw new 
LowLevelPutException(LowLevelPutException.INTERNAL_ERROR);
                }
@@ -1023,7 +1044,7 @@
                                }
                        }
                } finally {
-                       node.unlockUID(uid, false, true, true, false, true);
+                       node.unlockUID(uid, false, true, true, false, true, 
tag);
                }
        }

@@ -1031,7 +1052,8 @@
                logMINOR = Logger.shouldLog(Logger.MINOR, this);
                SSKInsertSender is;
                long uid = random.nextLong();
-               if(!node.lockUID(uid, true, true, false, true)) {
+               InsertTag tag = new InsertTag(true, InsertTag.START.LOCAL);
+               if(!node.lockUID(uid, true, true, false, true, tag)) {
                        Logger.error(this, "Could not lock UID just randomly 
generated: " + uid + " - probably indicates broken PRNG");
                        throw new 
LowLevelPutException(LowLevelPutException.INTERNAL_ERROR);
                }
@@ -1149,7 +1171,7 @@
                                }
                        }
                } finally {
-                       node.unlockUID(uid, true, true, true, false, true);
+                       node.unlockUID(uid, true, true, true, false, true, tag);
                }
        }


Modified: branches/db4o/freenet/src/freenet/node/NodeCrypto.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/NodeCrypto.java      2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/NodeCrypto.java      2009-01-22 
18:32:20 UTC (rev 25217)
@@ -62,6 +62,8 @@
        byte[] identityHash;
        /** Hash of hash of identity i.e. hash of setup key. */
        byte[] identityHashHash;
+       /** Nonce used to generate ?secureid= for fproxy etc */
+       byte[] clientNonce;
        /** My crypto group */
        private DSAGroup cryptoGroup;
        /** My private key */
@@ -235,6 +237,18 @@
                }
                myARK = ark;

+               String cn = fs.get("clientNonce");
+               if(cn != null) {
+                       try {
+                               clientNonce = Base64.decode(cn);
+                       } catch (IllegalBase64Exception e) {
+                               throw new IOException("Invalid clientNonce 
field: "+e);
+                       }
+               } else {
+                       clientNonce = new byte[32];
+                       node.random.nextBytes(clientNonce);
+               }
+               
        }

        /**
@@ -253,6 +267,8 @@
                myARKNumber = 0;
                SHA256.returnMessageDigest(md);
                anonSetupCipher.initialize(identityHash);
+               clientNonce = new byte[32];
+               node.random.nextBytes(clientNonce);
        }

        public void start(boolean disableHangchecker) {
@@ -437,6 +453,7 @@
                // We must save the location!
                if(fs.get("location") == null)
                        fs.put("location", node.lm.getLocation());
+               fs.putSingle("clientNonce", Base64.encode(clientNonce));

        }


Modified: branches/db4o/freenet/src/freenet/node/NodeDispatcher.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/NodeDispatcher.java  2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/NodeDispatcher.java  2009-01-22 
18:32:20 UTC (rev 25217)
@@ -261,7 +261,8 @@

                // Do we want it? We can RejectOverload if we don't have the 
bandwidth...
                boolean isSSK = key instanceof NodeSSK;
-               node.lockUID(uid, isSSK, false, true, false);
+               OfferReplyTag tag = new OfferReplyTag(isSSK);
+               node.lockUID(uid, isSSK, false, true, false, tag);
                boolean needPubKey;
                try {
                needPubKey = m.getBoolean(DMT.NEED_PUB_KEY);
@@ -275,22 +276,22 @@
                        } catch (NotConnectedException e) {
                                Logger.normal(this, "Rejecting (overload) data 
request from "+source.getPeer()+": "+e);
                        }
-                       node.unlockUID(uid, isSSK, false, false, true, false);
+                       node.unlockUID(uid, isSSK, false, false, true, false, 
tag);
                        return true;
                }

                } catch (Error e) {
-                       node.unlockUID(uid, isSSK, false, false, true, false);
+                       node.unlockUID(uid, isSSK, false, false, true, false, 
tag);
                        throw e;
                } catch (RuntimeException e) {
-                       node.unlockUID(uid, isSSK, false, false, true, false);
+                       node.unlockUID(uid, isSSK, false, false, true, false, 
tag);
                        throw e;
                } // Otherwise, sendOfferedKey is responsible for unlocking. 

                // Accept it.

                try {
-                       node.failureTable.sendOfferedKey(key, isSSK, 
needPubKey, uid, source);
+                       node.failureTable.sendOfferedKey(key, isSSK, 
needPubKey, uid, source, tag);
                } catch (NotConnectedException e) {
                        // Too bad.
                }
@@ -354,7 +355,8 @@
                }
         short htl = m.getShort(DMT.HTL);
         Key key = (Key) m.getObject(DMT.FREENET_ROUTING_KEY);
-               if(!node.lockUID(id, isSSK, false, false, false)) {
+        final RequestTag tag = new RequestTag(isSSK, RequestTag.START.REMOTE);
+               if(!node.lockUID(id, isSSK, false, false, false, tag)) {
                        if(logMINOR) Logger.minor(this, "Could not lock ID 
"+id+" -> rejecting (already running)");
                        Message rejected = DMT.createFNPRejectedLoop(id);
                        try {
@@ -377,7 +379,8 @@
                        } catch (NotConnectedException e) {
                                Logger.normal(this, "Rejecting (overload) data 
request from "+source.getPeer()+": "+e);
                        }
-                       node.unlockUID(id, isSSK, false, false, false, false);
+                       tag.setRejected();
+                       node.unlockUID(id, isSSK, false, false, false, false, 
tag);
                        // Do not tell failure table.
                        // Otherwise an attacker can flood us with requests 
very cheaply and purge our
                        // failure table even though we didn't accept any of 
them.
@@ -385,7 +388,7 @@
                }
                
nodeStats.reportIncomingRequestLocation(key.toNormalizedDouble());
                //if(!node.lockUID(id)) return false;
-               RequestHandler rh = new RequestHandler(m, source, id, node, 
htl, key);
+               RequestHandler rh = new RequestHandler(m, source, id, node, 
htl, key, tag);
                node.executor.execute(rh, "RequestHandler for UID "+id+" on 
"+node.getDarknetPortNumber());
                return true;
        }
@@ -402,7 +405,8 @@
                        }
                        return true;
                }
-               if(!node.lockUID(id, isSSK, true, false, false)) {
+               InsertTag tag = new InsertTag(isSSK, InsertTag.START.REMOTE);
+               if(!node.lockUID(id, isSSK, true, false, false, tag)) {
                        if(logMINOR) Logger.minor(this, "Could not lock ID 
"+id+" -> rejecting (already running)");
                        Message rejected = DMT.createFNPRejectedLoop(id);
                        try {
@@ -422,7 +426,7 @@
                        } catch (NotConnectedException e) {
                                Logger.normal(this, "Rejecting (overload) 
insert request from "+source.getPeer()+": "+e);
                        }
-                       node.unlockUID(id, isSSK, true, false, false, false);
+                       node.unlockUID(id, isSSK, true, false, false, false, 
tag);
                        return true;
                }
                long now = System.currentTimeMillis();
@@ -431,17 +435,17 @@
                byte[] data = ((ShortBuffer) m.getObject(DMT.DATA)).getData();
                byte[] headers = ((ShortBuffer) 
m.getObject(DMT.BLOCK_HEADERS)).getData();
                short htl = m.getShort(DMT.HTL);
-                       SSKInsertHandler rh = new SSKInsertHandler(key, data, 
headers, htl, source, id, node, now);
+                       SSKInsertHandler rh = new SSKInsertHandler(key, data, 
headers, htl, source, id, node, now, tag);
                rh.receivedBytes(m.receivedByteCount());
                        node.executor.execute(rh, "SSKInsertHandler for "+id+" 
on "+node.getDarknetPortNumber());
                } else if(m.getSpec().equals(DMT.FNPSSKInsertRequestNew)) {
                        NodeSSK key = (NodeSSK) 
m.getObject(DMT.FREENET_ROUTING_KEY);
                        short htl = m.getShort(DMT.HTL);
-                       SSKInsertHandler rh = new SSKInsertHandler(key, null, 
null, htl, source, id, node, now);
+                       SSKInsertHandler rh = new SSKInsertHandler(key, null, 
null, htl, source, id, node, now, tag);
                rh.receivedBytes(m.receivedByteCount());
                        node.executor.execute(rh, "SSKInsertHandler for "+id+" 
on "+node.getDarknetPortNumber());
                } else {
-                       CHKInsertHandler rh = new CHKInsertHandler(m, source, 
id, node, now);
+                       CHKInsertHandler rh = new CHKInsertHandler(m, source, 
id, node, now, tag);
                        node.executor.execute(rh, "CHKInsertHandler for "+id+" 
on "+node.getDarknetPortNumber());
                }
                if(logMINOR) Logger.minor(this, "Started InsertHandler for 
"+id);
@@ -579,7 +583,7 @@
         */
        private boolean handleRoutedRejected(Message m) {
                long id = m.getLong(DMT.UID);
-               Long lid = new Long(id);
+               Long lid = Long.valueOf(id);
                RoutedContext rc = routedContexts.get(lid);
                if(rc == null) {
                        // Gah
@@ -618,7 +622,7 @@
                if(logMINOR) Logger.minor(this, "handleRouted("+m+ ')');

                long id = m.getLong(DMT.UID);
-               Long lid = new Long(id);
+               Long lid = Long.valueOf(id);
                short htl = m.getShort(DMT.HTL);
                byte[] identity = ((ShortBuffer) 
m.getObject(DMT.NODE_IDENTITY)).getData();
                if(source != null) htl = source.decrementHTL(htl);
@@ -661,7 +665,7 @@
        boolean handleRoutedReply(Message m) {
                long id = m.getLong(DMT.UID);
                if(logMINOR) Logger.minor(this, "Got reply: "+m);
-               Long lid = new Long(id);
+               Long lid = Long.valueOf(id);
                RoutedContext ctx = routedContexts.get(lid);
                if(ctx == null) {
                        Logger.error(this, "Unrecognized routed reply: "+m);

Modified: branches/db4o/freenet/src/freenet/node/NodeStarter.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/NodeStarter.java     2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/NodeStarter.java     2009-01-22 
18:32:20 UTC (rev 25217)
@@ -76,7 +76,7 @@
        public Integer start(String[] args) {
                if(args.length > 1) {
                        System.out.println("Usage: $ java freenet.node.Node 
<configFile>");
-                       return new Integer(-1);
+                       return Integer.valueOf(-1);
                }

                getExtBuild();
@@ -98,7 +98,7 @@
                } catch(IOException e) {
                        System.out.println("Error : " + e);
                        e.printStackTrace();
-                       return new Integer(-1);
+                       return Integer.valueOf(-1);
                }

                // First, set up logging. It is global, and may be shared 
between several nodes.
@@ -111,7 +111,7 @@
                } catch(InvalidConfigValueException e) {
                        System.err.println("Error: could not set up logging: " 
+ e.getMessage());
                        e.printStackTrace();
-                       return new Integer(-2);
+                       return Integer.valueOf(-2);
                }

                executor.start();

Modified: branches/db4o/freenet/src/freenet/node/NodeStats.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/NodeStats.java       2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/NodeStats.java       2009-01-22 
18:32:20 UTC (rev 25217)
@@ -3,8 +3,6 @@
 import java.io.File;
 import java.text.DecimalFormat;
 import java.text.NumberFormat;
-import java.util.Arrays;
-import java.util.Comparator;

 import freenet.config.InvalidConfigValueException;
 import freenet.config.SubConfig;
@@ -134,7 +132,13 @@
        final TrivialRunningAverage localFetchPSuccess;
        final TrivialRunningAverage remoteFetchPSuccess;
        final TrivialRunningAverage blockTransferPSuccess;
+       final TrivialRunningAverage blockTransferFailTurtled;
+       final TrivialRunningAverage blockTransferFailTimeout;

+       final TrivialRunningAverage successfulLocalCHKFetchTimeAverage;
+       final TrivialRunningAverage unsuccessfulLocalCHKFetchTimeAverage;
+       final TrivialRunningAverage localCHKFetchTimeAverage;
+       
        private long previous_input_stat;
        private long previous_output_stat;
        private long previous_io_stat_time;
@@ -343,7 +347,13 @@
                localFetchPSuccess = new TrivialRunningAverage();
                remoteFetchPSuccess = new TrivialRunningAverage();
                blockTransferPSuccess = new TrivialRunningAverage();
+               blockTransferFailTurtled = new TrivialRunningAverage();
+               blockTransferFailTimeout = new TrivialRunningAverage();

+               successfulLocalCHKFetchTimeAverage = new 
TrivialRunningAverage();
+               unsuccessfulLocalCHKFetchTimeAverage = new 
TrivialRunningAverage();
+               localCHKFetchTimeAverage = new TrivialRunningAverage();
+               
                requestOutputThrottle = 
                        new TokenBucket(Math.max(obwLimit*60, 32768*20), 
(int)((1000L*1000L*1000L) / (obwLimit)), 0);
                requestInputThrottle = 
@@ -625,6 +635,138 @@
                        return "Input bandwidth liability 
("+bandwidthLiabilityInput+" > "+bandwidthAvailableInput+")";
                }

+//             // We want fast transfers!
+//             // We want it to be *possible* for all transfers currently 
running to complete in a short period.
+//             // This does NOT assume they are all successful, it uses the 
averages.
+//             // As of 09/01/09, the typical successful CHK fetch takes 
around 18 seconds ...
+//             
+//             // Accept a transfer if our *current* load can be completed in 
the target time.
+//             // We do not care what the new request we are considering is.
+//             // This is more or less equivalent to what we do above but lets 
more requests through.
+//             
+//             numRemoteCHKRequests--;
+//             numRemoteSSKRequests--;
+//             numRemoteCHKInserts--;
+//             numRemoteSSKInserts--;
+//             numLocalCHKRequests--;
+//             numLocalSSKRequests--;
+//             numLocalCHKInserts--;
+//             numLocalSSKInserts--;
+//             
+//             final double TRANSFER_EVERYTHING_TIME = 5.0; // 5 seconds target
+//             
+//             double completionBandwidthOutput;
+//             if(ignoreLocalVsRemoteBandwidthLiability) {
+//                     completionBandwidthOutput = 
+//                             remoteChkFetchBytesSentAverage.currentValue() * 
(numRemoteCHKRequests + numLocalCHKRequests) +
+//                             remoteSskFetchBytesSentAverage.currentValue() * 
(numRemoteSSKRequests + numLocalSSKRequests) +
+//                             remoteChkInsertBytesSentAverage.currentValue() 
* (numRemoteCHKInserts + numLocalCHKInserts) +
+//                             remoteSskInsertBytesSentAverage.currentValue() 
* (numRemoteSSKInserts + numLocalSSKInserts);
+//             } else {
+//             completionBandwidthOutput =
+//                     remoteChkFetchBytesSentAverage.currentValue() * 
numRemoteCHKRequests +
+//                     localChkFetchBytesSentAverage.currentValue() * 
numLocalCHKRequests +
+//                     remoteSskFetchBytesSentAverage.currentValue() * 
numRemoteSSKRequests +
+//                     localSskFetchBytesSentAverage.currentValue() * 
numLocalSSKRequests +
+//                     remoteChkInsertBytesSentAverage.currentValue() * 
numRemoteCHKInserts +
+//                     localChkInsertBytesSentAverage.currentValue() * 
numLocalCHKInserts +
+//                     remoteSskInsertBytesSentAverage.currentValue() * 
numRemoteSSKInserts +
+//                     localSskInsertBytesSentAverage.currentValue() * 
numLocalSSKInserts +
+//                     successfulChkOfferReplyBytesSentAverage.currentValue() 
* numCHKOfferReplies +
+//                     successfulSskOfferReplyBytesSentAverage.currentValue() 
* numSSKOfferReplies;
+//             }
+//             
+//             int outputLimit = node.getOutputBandwidthLimit();
+//             
+//             double outputBandwidthAvailableInTargetTime = outputLimit * 
TRANSFER_EVERYTHING_TIME;
+//             
+//             // Increase the target for slow nodes.
+//             
+//             double minimum =
+//                     remoteChkFetchBytesSentAverage.currentValue() +
+//                     localChkFetchBytesSentAverage.currentValue() +
+//                     remoteSskFetchBytesSentAverage.currentValue() +
+//                     localSskFetchBytesSentAverage.currentValue() +
+//                     remoteChkInsertBytesSentAverage.currentValue() +
+//                     localChkInsertBytesSentAverage.currentValue() +
+//                     remoteSskInsertBytesSentAverage.currentValue() +
+//                     localSskInsertBytesSentAverage.currentValue() +
+//                     successfulChkOfferReplyBytesSentAverage.currentValue() +
+//                     successfulSskOfferReplyBytesSentAverage.currentValue();
+//             minimum /= 2; // roughly one of each type, averaged over remote 
and local; FIXME get a real non-specific average
+//             
+//             if(outputBandwidthAvailableInTargetTime < minimum) {
+//                     outputBandwidthAvailableInTargetTime = minimum;
+//                     if(logMINOR) Logger.minor(this, "Increased minimum time 
to transfer everything to "+(minimum / outputLimit)+"s = "+minimum+"B to 
compensate for slow node");
+//             }
+//             
+//             if(logMINOR) Logger.minor(this, TRANSFER_EVERYTHING_TIME+" 
second limit: "+outputBandwidthAvailableInTargetTime+" expected transfers: 
"+completionBandwidthOutput);
+//             
+//             if(completionBandwidthOutput > 
outputBandwidthAvailableInTargetTime) {
+//                     pInstantRejectIncoming.report(1.0);
+//                     rejected("Transfer speed (output)", isLocal);
+//                     return "Transfer speed (output) 
("+bandwidthLiabilityOutput+" > "+bandwidthAvailableOutput+")";
+//             }
+//             
+//             
+//             
+//             double completionBandwidthInput;
+//             if(ignoreLocalVsRemoteBandwidthLiability) {
+//                     completionBandwidthInput =
+//                             
remoteChkFetchBytesReceivedAverage.currentValue() * (numRemoteCHKRequests + 
numLocalCHKRequests) +
+//                             
remoteSskFetchBytesReceivedAverage.currentValue() * (numRemoteSSKRequests + 
numLocalSSKRequests) +
+//                             
remoteChkInsertBytesReceivedAverage.currentValue() * (numRemoteCHKInserts + 
numLocalCHKInserts) +
+//                             
remoteSskInsertBytesReceivedAverage.currentValue() * (numRemoteSSKInserts + 
numLocalSSKInserts);
+//             } else {
+//             completionBandwidthInput =
+//                     // For receiving data, local requests are the same as 
remote ones
+//                     remoteChkFetchBytesReceivedAverage.currentValue() * 
numRemoteCHKRequests +
+//                     localChkFetchBytesReceivedAverage.currentValue() * 
numLocalCHKRequests +
+//                     remoteSskFetchBytesReceivedAverage.currentValue() * 
numRemoteSSKRequests +
+//                     localSskFetchBytesReceivedAverage.currentValue() * 
numLocalSSKRequests +
+//                     // Local inserts don't receive the data to relay, so 
use the local variant
+//                     remoteChkInsertBytesReceivedAverage.currentValue() * 
numRemoteCHKInserts +
+//                     localChkInsertBytesReceivedAverage.currentValue() * 
numLocalCHKInserts +
+//                     remoteSskInsertBytesReceivedAverage.currentValue() * 
numRemoteSSKInserts +
+//                     localSskInsertBytesReceivedAverage.currentValue() * 
numLocalSSKInserts +
+//                     
successfulChkOfferReplyBytesReceivedAverage.currentValue() * numCHKOfferReplies 
+
+//                     
successfulSskOfferReplyBytesReceivedAverage.currentValue() * numSSKOfferReplies;
+//             }
+//             int inputLimit = node.getInputBandwidthLimit();
+//             double inputBandwidthAvailableInTargetTime =
+//                     inputLimit * TRANSFER_EVERYTHING_TIME;
+//             
+//             // Increase the target for slow nodes.
+//             
+//             minimum =
+//                     remoteChkFetchBytesReceivedAverage.currentValue() +
+//                     localChkFetchBytesReceivedAverage.currentValue() +
+//                     remoteSskFetchBytesReceivedAverage.currentValue() +
+//                     localSskFetchBytesReceivedAverage.currentValue() +
+//                     remoteChkInsertBytesReceivedAverage.currentValue() +
+//                     localChkInsertBytesReceivedAverage.currentValue() +
+//                     remoteSskInsertBytesReceivedAverage.currentValue() +
+//                     localSskInsertBytesReceivedAverage.currentValue() +
+//                     
successfulChkOfferReplyBytesReceivedAverage.currentValue() +
+//                     
successfulSskOfferReplyBytesReceivedAverage.currentValue();
+//             minimum /= 2; // roughly one of each type, averaged over remote 
and local; FIXME get a real non-specific average
+//             
+//             if(inputBandwidthAvailableInTargetTime < minimum) {
+//                     inputBandwidthAvailableInTargetTime = minimum;
+//                     if(logMINOR) Logger.minor(this, "Increased minimum time 
to transfer everything (input) to "+(minimum / inputLimit)+"s = "+minimum+"B to 
compensate for slow node");
+//             }
+//             
+//
+//             
+//             if(bandwidthAvailableInput < 0){
+//                     Logger.error(this, "Negative available bandwidth: 
"+inputBandwidthAvailableInTargetTime+" 
node.ibwlimit="+node.getInputBandwidthLimit()+" 
node.obwlimit="+node.getOutputBandwidthLimit()+" 
node.inputLimitDefault="+node.inputLimitDefault);
+//             }
+//             if(completionBandwidthInput > 
inputBandwidthAvailableInTargetTime) {
+//                     pInstantRejectIncoming.report(1.0);
+//                     rejected("Transfer speed (input)", isLocal);
+//                     return "Transfer speed (input) 
("+bandwidthLiabilityInput+" > "+bandwidthAvailableInput+")";
+//             }
+               
                // Do we have the bandwidth?
                double expected = this.getThrottle(isLocal, isInsert, isSSK, 
true).currentValue();
                int expectedSent = (int)Math.max(expected / overheadFraction, 
0);
@@ -905,6 +1047,11 @@
                }
                fs.put("averagePingTime", getNodeAveragePingTime());
                fs.put("bwlimitDelayTime", getBwlimitDelayTime());
+               fs.put("opennetSizeEstimateSession", 
getOpennetSizeEstimate(-1));
+               int opennetSizeEstimate24hourRecent = 
getOpennetSizeEstimate(now - (24 * 60 * 60 * 1000)); // 24 hours
+               fs.put("opennetSizeEstimate24hourRecent", 
opennetSizeEstimate24hourRecent);
+               int opennetSizeEstimate48hourRecent = 
getOpennetSizeEstimate(now - (48 * 60 * 60 * 1000)); // 48 hours
+               fs.put("opennetSizeEstimate48hourRecent", 
opennetSizeEstimate48hourRecent);             
                fs.put("networkSizeEstimateSession", 
getDarknetSizeEstimate(-1));
                int networkSizeEstimate24hourRecent = 
getDarknetSizeEstimate(now - (24*60*60*1000));  // 24 hours
                fs.put("networkSizeEstimate24hourRecent", 
networkSizeEstimate24hourRecent);
@@ -916,17 +1063,11 @@
                fs.put("unclaimedFIFOSize", node.usm.getUnclaimedFIFOSize());

                /* gather connection statistics */
-               DarknetPeerNodeStatus[] peerNodeStatuses = 
peers.getDarknetPeerNodeStatuses(true);
-               Arrays.sort(peerNodeStatuses, new 
Comparator<DarknetPeerNodeStatus>() {
-                       public int compare(DarknetPeerNodeStatus firstNode, 
DarknetPeerNodeStatus secondNode) {
-                               int statusDifference = 
firstNode.getStatusValue() - secondNode.getStatusValue();
-                               if (statusDifference != 0) {
-                                       return statusDifference;
-                               }
-                               return 
firstNode.getName().compareToIgnoreCase(secondNode.getName());
-                       }
-               });
+               PeerNodeStatus[] peerNodeStatuses = 
peers.getPeerNodeStatuses(true);

+               int numberOfSeedServers = getCountSeedServers(peerNodeStatuses);
+               int numberOfSeedClients = getCountSeedClients(peerNodeStatuses);
+               
                int numberOfConnected = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_CONNECTED);
                int numberOfRoutingBackedOff = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_ROUTING_BACKED_OFF);
                int numberOfTooNew = 
PeerNodeStatus.getPeerStatusCount(peerNodeStatuses, 
PeerManager.PEER_NODE_STATUS_TOO_NEW);
@@ -941,6 +1082,8 @@
                int numberOfSimpleConnected = numberOfConnected + 
numberOfRoutingBackedOff;
                int numberOfNotConnected = numberOfTooNew + numberOfTooOld + 
numberOfDisconnected + numberOfNeverConnected + numberOfDisabled + 
numberOfBursting + numberOfListening + numberOfListenOnly;

+               fs.put("numberOfSeedServers", numberOfSeedServers);
+               fs.put("numberOfSeedClients", numberOfSeedClients);
                fs.put("numberOfConnected", numberOfConnected);
                fs.put("numberOfRoutingBackedOff", numberOfRoutingBackedOff);
                fs.put("numberOfTooNew", numberOfTooNew);
@@ -1149,7 +1292,9 @@
                                sskFetchPSuccess,
                                localFetchPSuccess,
                                remoteFetchPSuccess,
-                               blockTransferPSuccess
+                               blockTransferPSuccess,
+                               blockTransferFailTurtled,
+                               blockTransferFailTimeout
                };
                final String[] names = new String[] {
                                // FIXME l10n, but atm this only shows up in 
advanced mode
@@ -1158,7 +1303,9 @@
                                "SSKs",
                                "Local requests",
                                "Remote requests",
-                               "Block transfers"
+                               "Block transfers",
+                               "Turtled downstream",
+                               "Transfers timed out"
                };
                HTMLNode row = list.addChild("tr");
                row.addChild("th", "Group"); 
@@ -1176,6 +1323,22 @@
                                row.addChild("td", 
thousendPoint.format(averages[i].countReports()));
                        }
                }
+               
+               row = list.addChild("tr");
+               row.addChild("td", "Turtle requests");
+               long total;
+               long succeeded;
+               synchronized(this) {
+                       total = turtleTransfersCompleted;
+                       succeeded = turtleSuccesses;
+               }
+               if(total == 0) {
+                       row.addChild("td", "-");
+                       row.addChild("td", "0");
+               } else {
+                       row.addChild("td", fix3p3pct.format((double)succeeded / 
total));
+                       row.addChild("td", thousendPoint.format(total));
+               }
        }

        /* Total bytes sent by requests, excluding payload */
@@ -1701,7 +1864,11 @@
                if(logMINOR) Logger.minor(this, "Successful receives: 
"+blockTransferPSuccess.currentValue()+" 
count="+blockTransferPSuccess.countReports());
        }

-       public synchronized void failedBlockReceive() {
+       public synchronized void failedBlockReceive(boolean normalFetch, 
boolean timeout, boolean turtle) {
+               if(normalFetch) {
+                       blockTransferFailTurtled.report(turtle ? 1.0 : 0.0);
+                       blockTransferFailTimeout.report(timeout ? 1.0 : 0.0);
+               }
                blockTransferPSuccess.report(0.0);
                if(logMINOR) Logger.minor(this, "Successful receives: 
"+blockTransferPSuccess.currentValue()+" 
count="+blockTransferPSuccess.countReports());
        }
@@ -1762,4 +1929,55 @@

                return result;
        }
+       
+       private int getCountSeedServers(PeerNodeStatus[] peerNodeStatuses) {
+               int count = 0;
+               for (int peerIndex = 0; peerIndex < peerNodeStatuses.length; 
peerIndex++) {
+                       if (peerNodeStatuses[peerIndex].isSeedServer())
+                               count++;
+               }
+               return count;
+       }
+
+       private int getCountSeedClients(PeerNodeStatus[] peerNodeStatuses) {
+               int count = 0;
+               for (int peerIndex = 0; peerIndex < peerNodeStatuses.length; 
peerIndex++) {
+                       if (peerNodeStatuses[peerIndex].isSeedClient())
+                               count++;
+               }
+               return count;
+       }
+
+       public void reportCHKTime(long rtt, boolean successful) {
+               if(successful)
+                       successfulLocalCHKFetchTimeAverage.report(rtt);
+               else
+                       unsuccessfulLocalCHKFetchTimeAverage.report(rtt);
+               localCHKFetchTimeAverage.report(rtt);
+       }
+
+       public void fillDetailedTimingsBox(HTMLNode html) {
+               HTMLNode table = html.addChild("table");
+               HTMLNode row = table.addChild("tr");
+               row.addChild("td", "Successful");
+               row.addChild("td", 
TimeUtil.formatTime((long)successfulLocalCHKFetchTimeAverage.currentValue(), 2, 
true));
+               row = table.addChild("tr");
+               row.addChild("td", "Unsuccessful");
+               row.addChild("td", 
TimeUtil.formatTime((long)unsuccessfulLocalCHKFetchTimeAverage.currentValue(), 
2, true));
+               row = table.addChild("tr");
+               row.addChild("td", "Average");
+               row.addChild("td", 
TimeUtil.formatTime((long)localCHKFetchTimeAverage.currentValue(), 2, true));
+       }
+       
+       private long turtleTransfersCompleted;
+       private long turtleSuccesses;
+       
+       synchronized void turtleSucceeded() {
+               turtleSuccesses++;
+               turtleTransfersCompleted++;
+       }
+       
+       synchronized void turtleFailed() {
+               turtleTransfersCompleted++;
+       }
 }

Copied: branches/db4o/freenet/src/freenet/node/OfferReplyTag.java (from rev 
25205, trunk/freenet/src/freenet/node/OfferReplyTag.java)
===================================================================
--- branches/db4o/freenet/src/freenet/node/OfferReplyTag.java                   
        (rev 0)
+++ branches/db4o/freenet/src/freenet/node/OfferReplyTag.java   2009-01-22 
18:32:20 UTC (rev 25217)
@@ -0,0 +1,27 @@
+package freenet.node;
+
+import freenet.support.Logger;
+import freenet.support.TimeUtil;
+
+/**
+ * Tag tracking an offer reply.
+ * @author Matthew Toseland <toad at amphibian.dyndns.org> (0xE43DA450)
+ */
+public class OfferReplyTag extends UIDTag {
+
+       final boolean ssk;
+       
+       public OfferReplyTag(boolean isSSK) {
+               super();
+               ssk = isSSK;
+       }
+
+       @Override
+       public void logStillPresent(Long uid) {
+               StringBuffer sb = new StringBuffer();
+               sb.append("Still present after 
").append(TimeUtil.formatTime(age()));
+               sb.append(" : ssk=").append(ssk);
+               Logger.error(this, sb.toString());
+       }
+
+}

Modified: branches/db4o/freenet/src/freenet/node/PacketSender.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/PacketSender.java    2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/PacketSender.java    2009-01-22 
18:32:20 UTC (rev 25217)
@@ -29,6 +29,9 @@
  *         Thread that sends a packet whenever: - A packet needs to be resent 
immediately -
  *         Acknowledgments or resend requests need to be sent urgently.
  */
+// j16sdiz (22-Dec-2008):
+// FIXME this is the only class implements Ticker, everbody is using this as 
+// a generic task scheduler. Either rename this class, or create another 
tricker for non-Packet tasks
 public class PacketSender implements Runnable, Ticker {

        private static boolean logMINOR;
@@ -55,7 +58,6 @@
        long lastReceivedPacketFromAnyNode;
        /** For watchdog. 32-bit to avoid locking. */
        volatile int lastTimeInSeconds;
-       private long timeLastSentOldOpennetConnectAttempt;
        private Vector<ResendPacketItem> rpiTemp;
        private int[] rpiIntTemp;
        private boolean started = false;
@@ -570,7 +572,7 @@
                Job job = new Job(name, runner);
                if(offset < 0) offset = 0;
                long now = System.currentTimeMillis();
-               Long l = new Long(offset + now);
+               Long l = Long.valueOf(offset + now);
                synchronized(timedJobsByTime) {
                        Object o = timedJobsByTime.get(l);
                        if(o == null)

Modified: branches/db4o/freenet/src/freenet/node/PeerManager.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/PeerManager.java     2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/PeerManager.java     2009-01-22 
18:32:20 UTC (rev 25217)
@@ -896,6 +896,10 @@
                                        Logger.minor(this, "Skipping (not 
connected): " + p.getPeer());
                                continue;
                        }
+                       if(key != null && p.isTurtling(key)) {
+                               if(logMINOR)
+                                       Logger.minor(this, "Skipping (already 
turtling key): "+p.getPeer());
+                       }
                        if(minVersion > 0 && 
Version.getArbitraryBuildNumber(p.getVersion(), -1) < minVersion) {
                                if(logMINOR)
                                        Logger.minor(this, "Skipping old 
version: " + p.getPeer());
@@ -1431,7 +1435,7 @@
         * Add a PeerNode status to the map
         */
        public void addPeerNodeStatus(int pnStatus, PeerNode peerNode, boolean 
noLog) {
-               Integer peerNodeStatus = new Integer(pnStatus);
+               Integer peerNodeStatus = Integer.valueOf(pnStatus);
                addPeerNodeStatuses(pnStatus, peerNode, peerNodeStatus, 
peerNodeStatuses, noLog);
                if(!peerNode.isOpennet())
                        addPeerNodeStatuses(pnStatus, peerNode, peerNodeStatus, 
peerNodeStatusesDarknet, noLog);
@@ -1462,7 +1466,7 @@
         * @param darknet If true, only count darknet nodes, if false, count 
all nodes.
         */
        public int getPeerNodeStatusSize(int pnStatus, boolean darknet) {
-               Integer peerNodeStatus = new Integer(pnStatus);
+               Integer peerNodeStatus = Integer.valueOf(pnStatus);
                HashSet<PeerNode> statusSet = null;
                HashMap<Integer, HashSet<PeerNode>> statuses = darknet ? 
peerNodeStatusesDarknet : this.peerNodeStatuses;
                synchronized(statuses) {
@@ -1479,7 +1483,7 @@
         * @param isInPeers If true, complain if the node is not in the peers 
list; if false, complain if it is.
         */
        public void removePeerNodeStatus(int pnStatus, PeerNode peerNode, 
boolean noLog) {
-               Integer peerNodeStatus = new Integer(pnStatus);
+               Integer peerNodeStatus = Integer.valueOf(pnStatus);
                removePeerNodeStatus(pnStatus, peerNodeStatus, peerNode, 
peerNodeStatuses, noLog);
                if(!peerNode.isOpennet())
                        removePeerNodeStatus(pnStatus, peerNodeStatus, 
peerNode, peerNodeStatusesDarknet, noLog);

Modified: branches/db4o/freenet/src/freenet/node/PeerMessageQueue.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/PeerMessageQueue.java        
2009-01-22 18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/PeerMessageQueue.java        
2009-01-22 18:32:20 UTC (rev 25217)
@@ -16,7 +16,7 @@

        private final PrioQueue[] queuesByPriority;

-       private class PrioQueue {
+       private static class PrioQueue {
                LinkedList<MessageItem> itemsNoID;
                ArrayList<LinkedList<MessageItem>> itemsWithID;
                ArrayList<Long> itemsIDs;

Modified: branches/db4o/freenet/src/freenet/node/PeerNode.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/PeerNode.java        2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/PeerNode.java        2009-01-22 
18:32:20 UTC (rev 25217)
@@ -4266,4 +4266,47 @@
                if(logMINOR) Logger.minor(this, "getReusableTrackerID(): 
"+cur.packets.trackerID+" on "+this);
                return cur.packets.trackerID;
        }
+
+       static final int MAX_TURTLES_PER_PEER = 3;
+       
+       private HashMap<Key,RequestSender> turtlingTransfers = new 
HashMap<Key,RequestSender>();
+       
+       public boolean registerTurtleTransfer(RequestSender sender) {
+               Key key = sender.key;
+               synchronized(turtlingTransfers) {
+                       if(turtlingTransfers.size() >= MAX_TURTLES_PER_PEER) {
+                               Logger.error(this, "Too many turtles for peer");
+                               return false;
+                       }
+                       if(turtlingTransfers.containsKey(key)) {
+                               Logger.error(this, "Already fetching key from 
peer");
+                               return false;
+                       }
+                       turtlingTransfers.put(key, sender);
+                       Logger.error(this, "Turtles for "+getPeer()+" : 
"+turtlingTransfers.size());
+                       return true;
+               }
+       }
+
+       public void unregisterTurtleTransfer(RequestSender sender) {
+               Key key = sender.key;
+               synchronized(turtlingTransfers) {
+                       if(!turtlingTransfers.containsKey(key)) {
+                               Logger.error(this, "Removing turtle transfer 
"+sender+" for "+key+" from "+this+" : DOES NOT EXIST");
+                               return;
+                       }
+                       RequestSender oldSender = turtlingTransfers.remove(key);
+                       if(oldSender != sender) {
+                               Logger.error(this, "Removing turtle transfer 
"+sender+" for "+key+" from "+this+" : WRONG SENDER: "+oldSender);
+                               turtlingTransfers.put(key, oldSender);
+                               return;
+                       }
+               }
+       }
+
+       public boolean isTurtling(Key key) {
+               synchronized(turtlingTransfers) {
+                       return turtlingTransfers.containsKey(key);
+               }
+       }
 }

Modified: branches/db4o/freenet/src/freenet/node/RequestHandler.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/RequestHandler.java  2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/RequestHandler.java  2009-01-22 
18:32:20 UTC (rev 25217)
@@ -49,18 +49,20 @@
        private long searchStartTime;
        private long responseDeadline;
        private BlockTransmitter bt;
+       private final RequestTag tag;

        @Override
        public String toString() {
                return super.toString() + " for " + uid;
        }

-       public RequestHandler(Message m, PeerNode source, long id, Node n, 
short htl, Key key) {
+       public RequestHandler(Message m, PeerNode source, long id, Node n, 
short htl, Key key, RequestTag tag) {
                req = m;
                node = n;
                uid = id;
                this.source = source;
                this.htl = htl;
+               this.tag = tag;
                if(htl <= 0)
                        htl = 1;
                this.key = key;
@@ -78,11 +80,13 @@
                } catch(NotConnectedException e) {
                        Logger.normal(this, "requestor gone, could not start 
request handler wait");
                        node.removeTransferringRequestHandler(uid);
-                       node.unlockUID(uid, key instanceof NodeSSK, false, 
false, false, false);
+                       tag.handlerThrew(e);
+                       node.unlockUID(uid, key instanceof NodeSSK, false, 
false, false, false, tag);
                } catch(Throwable t) {
                        Logger.error(this, "Caught " + t, t);
                        node.removeTransferringRequestHandler(uid);
-                       node.unlockUID(uid, key instanceof NodeSSK, false, 
false, false, false);
+                       tag.handlerThrew(t);
+                       node.unlockUID(uid, key instanceof NodeSSK, false, 
false, false, false, tag);
                }
        }
        private Exception previousApplyByteCountCall;
@@ -140,6 +144,7 @@

                Object o = node.makeRequestSender(key, htl, uid, source, false, 
true, false, false);
                if(o instanceof KeyBlock) {
+                       tag.setServedFromDatastore();
                        returnLocalData((KeyBlock) o);
                        return;
                }
@@ -193,9 +198,25 @@
                        synchronized(this) {
                                disconnected = true;
                        }
+                       tag.handlerDisconnected();
                        Logger.normal(this, "requestor is gone, can't begin CHK 
transfer");
                }
        }
+       
+       public void onAbortDownstreamTransfers(int reason, String desc) {
+               if(bt == null) {
+                       Logger.error(this, "No downstream transfer to abort! on 
"+this);
+                       return;
+               }
+               if(logMINOR)
+                       Logger.minor(this, "Aborting downstream transfer on 
"+this);
+               tag.onAbortDownstreamTransfers(reason, desc);
+               try {
+                       bt.abortSend(reason, desc);
+               } catch (NotConnectedException e) {
+                       // Ignore
+               }
+       }

        private void waitAndFinishCHKTransferOffThread() {
                node.executor.execute(new Runnable() {
@@ -209,7 +230,7 @@
                                        unregisterRequestHandlerWithNode();
                                }
                        }
-               }, "Finish CHK transfer for " + key);
+               }, "Finish CHK transfer for " + key + " for " + this);
        }

        private void waitAndFinishCHKTransfer() throws NotConnectedException {
@@ -229,6 +250,7 @@
        }

        public void onRequestSenderFinished(int status) {
+               if(logMINOR) Logger.minor(this, 
"onRequestSenderFinished("+status+") on "+this);
                long now = System.currentTimeMillis();
                this.status = status;

@@ -288,6 +310,8 @@
                                                        sendTerminal(reject);
                                                } else if(!disconnected)
                                                        
waitAndFinishCHKTransferOffThread();
+                                               else
+                                                       
unregisterRequestHandlerWithNode();
                                        return;
                                case RequestSender.VERIFY_FAILURE:
                                case RequestSender.GET_OFFER_VERIFY_FAILURE:
@@ -301,6 +325,8 @@
                                                } else if(!disconnected)
                                                        //Verify fails after 
receive() is complete, so we might as well propagate it...
                                                        
waitAndFinishCHKTransferOffThread();
+                                               else
+                                                       
unregisterRequestHandlerWithNode();
                                                return;
                                        }
                                        reject = 
DMT.createFNPRejectedOverload(uid, true);
@@ -317,6 +343,8 @@
                                                        sendTerminal(reject);
                                                } else if(!disconnected)
                                                        
waitAndFinishCHKTransferOffThread();
+                                               else
+                                                       
unregisterRequestHandlerWithNode();
                                                return;
                                        }
                                        Logger.error(this, 
"finish(TRANSFER_FAILED) should not be called on SSK?!?!", new 
Exception("error"));
@@ -436,7 +464,7 @@

        private void unregisterRequestHandlerWithNode() {
                node.removeTransferringRequestHandler(uid);
-               node.unlockUID(uid, key instanceof NodeSSK, false, false, 
false, false);
+               node.unlockUID(uid, key instanceof NodeSSK, false, false, 
false, false, tag);
        }

        /**

Modified: branches/db4o/freenet/src/freenet/node/RequestSender.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/RequestSender.java   2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/RequestSender.java   2009-01-22 
18:32:20 UTC (rev 25217)
@@ -33,7 +33,9 @@
 import freenet.support.Logger;
 import freenet.support.ShortBuffer;
 import freenet.support.SimpleFieldSet;
+import freenet.support.TimeUtil;
 import freenet.support.io.NativeThread;
+import freenet.support.math.MedianMeanRunningAverage;

 /**
  * @author amphibian
@@ -73,6 +75,11 @@
     private byte[] sskData;
     private SSKBlock block;
     private boolean hasForwarded;
+    private PeerNode transferringFrom;
+    private boolean turtleMode;
+    private boolean sentBackoffTurtle;
+    /** Set when we start to think about going to turtle mode - not unset if 
we get cancelled instead. */
+    private boolean tryTurtle;

     /** If true, only try to fetch the key from nodes which have offered it */
     private boolean tryOffersOnly;
@@ -97,6 +104,7 @@
     static final int GET_OFFER_TRANSFER_FAILED = 11;
     private PeerNode successFrom;
     private PeerNode lastNode;
+    private final long startTime;

     static String getStatusString(int status) {
        switch(status) {
@@ -148,6 +156,7 @@
     public RequestSender(Key key, DSAPublicKey pubKey, short htl, long uid, 
Node n,
             PeerNode source, boolean offersOnly) {
        if(key.getRoutingKey() == null) throw new NullPointerException();
+       startTime = System.currentTimeMillis();
         this.key = key;
         this.pubKey = pubKey;
         this.htl = htl;
@@ -264,12 +273,12 @@
                                }
                                fireCHKTransferBegins();

-                               BlockReceiver br = new BlockReceiver(node.usm, 
pn, uid, prb, this);
+                               BlockReceiver br = new BlockReceiver(node.usm, 
pn, uid, prb, this, node.getTicker(), true);

                                try {
                                        if(logMINOR) Logger.minor(this, 
"Receiving data");
                                        byte[] data = br.receive();
-                                       pn.transferSuccess();
+                                               pn.transferSuccess();
                                        if(logMINOR) Logger.minor(this, 
"Received data");
                                        // Received data
                                        try {
@@ -290,9 +299,10 @@
                                                                // A certain 
number of these are normal, it's better to track them through statistics than 
call attention to them in the logs.
                                                                
Logger.normal(this, "Transfer for offer failed 
("+e.getReason()+"/"+RetrievalException.getErrString(e.getReason())+"): "+e+" 
from "+pn, e);
                                        finish(GET_OFFER_TRANSFER_FAILED, pn, 
true);
-                                       
pn.transferFailed("RequestSenderGetOfferedTransferFailed");
+                                       // Backoff here anyway - the node 
really ought to have it!
+                                               
pn.transferFailed("RequestSenderGetOfferedTransferFailed");
                                offers.deleteLastOffer();
-                                       node.nodeStats.failedBlockReceive();
+                                       
node.nodeStats.failedBlockReceive(false, false, false);
                                        return;
                                }
                        } finally {
@@ -766,13 +776,57 @@
                                }
                                fireCHKTransferBegins();

-                               BlockReceiver br = new BlockReceiver(node.usm, 
next, uid, prb, this);
+                               long tStart = System.currentTimeMillis();
+                               BlockReceiver br = new BlockReceiver(node.usm, 
next, uid, prb, this, node.getTicker(), true);

                                try {
                                        if(logMINOR) Logger.minor(this, 
"Receiving data");
-                                       byte[] data = br.receive();
-                                       next.transferSuccess();
+                                       final PeerNode from = next;
+                                       synchronized(this) {
+                                               transferringFrom = next;
+                                       }
+                                       node.getTicker().queueTimedJob(new 
Runnable() {
+
+                                                               public void 
run() {
+                                                                       
synchronized(RequestSender.this) {
+                                                                               
if(transferringFrom != from) return;
+                                                                       }
+                                                                       
makeTurtle();
+                                                               }
+                                               
+                                       }, 60*1000);
+                                       byte[] data;
+                                       try {
+                                               data = br.receive();
+                                       } finally {
+                                               synchronized(this) {
+                                                       transferringFrom = null;
+                                               }
+                                       }
+                                       
+                                       long tEnd = System.currentTimeMillis();
+                                       this.transferTime = tEnd - tStart;
+                                       boolean turtle;
+                                       boolean turtleBackedOff;
+                                       synchronized(this) {
+                                               turtle = turtleMode;
+                                               turtleBackedOff = 
sentBackoffTurtle;
+                                               sentBackoffTurtle = true;
+                                       }
+                                       if(!turtle)
+                                               next.transferSuccess();
+                                       else {
+                                               Logger.error(this, "TURTLE 
SUCCEEDED: "+key+" for "+this+" in "+TimeUtil.formatTime(transferTime, 2, 
true));
+                                               if(!turtleBackedOff)
+                                                       
next.transferFailed("Turtled transfer");
+                                               
node.nodeStats.turtleSucceeded();
+                                       }
                                next.successNotOverload();
+                               if(turtle) {
+                                       next.unregisterTurtleTransfer(this);
+                                       node.unregisterTurtleTransfer(this);
+                               }
+                               node.nodeStats.successfulBlockReceive();
                                        if(logMINOR) Logger.minor(this, 
"Received data");
                                        // Received data
                                        try {
@@ -786,6 +840,20 @@
                                        finish(SUCCESS, next, false);
                                        return;
                                } catch (RetrievalException e) {
+                                       boolean turtle;
+                                       synchronized(this) {
+                                               turtle = turtleMode;
+                                       }
+                                       if(turtle) {
+                                               if(e.getReason() != 
RetrievalException.GONE_TO_TURTLE_MODE) {
+                                                       Logger.error(this, 
"TURTLE FAILED: "+key+" for "+this+" : "+e);
+                                                       
node.nodeStats.turtleFailed();
+                                               } else {
+                                                       if(logMINOR) 
Logger.minor(this, "Upstream turtled for "+this+" from "+next);
+                                               }
+                                       next.unregisterTurtleTransfer(this);
+                                       node.unregisterTurtleTransfer(this);
+                                       }
                                                        if 
(e.getReason()==RetrievalException.SENDER_DISCONNECTED)
                                                                
Logger.normal(this, "Transfer failed (disconnect): "+e, e);
                                                        else
@@ -794,7 +862,20 @@
                                                        
next.localRejectedOverload("TransferFailedRequest"+e.getReason());
                                        finish(TRANSFER_FAILED, next, false);
                                        node.failureTable.onFinalFailure(key, 
next, htl, FailureTable.REJECT_TIME, source);
-                                       
next.transferFailed("RequestSenderTransferFailed");
+                                       int reason = e.getReason();
+                                       boolean timeout = (!br.senderAborted()) 
&&
+                                                       (reason == 
RetrievalException.SENDER_DIED || reason == RetrievalException.RECEIVER_DIED || 
reason == RetrievalException.TIMED_OUT
+                                                       || reason == 
RetrievalException.UNABLE_TO_SEND_BLOCK_WITHIN_TIMEOUT);
+                                               if(timeout) {
+                                                       // Looks like a 
timeout. Backoff, even if it's a turtle.
+                                                       
next.transferFailed(e.getMessage());
+                                               } else {
+                                                       // Quick failure (in 
that we didn't have to timeout). Don't backoff.
+                                                       // Treat as a DNF.
+                                                       // If it was turtled, 
and then failed, still treat it as a DNF.
+                                                       
node.failureTable.onFinalFailure(key, next, htl, FailureTable.REJECT_TIME, 
source);
+                                               }
+                                       node.nodeStats.failedBlockReceive(true, 
timeout, reason == RetrievalException.GONE_TO_TURTLE_MODE);
                                        return;
                                }
                        } finally {
@@ -899,6 +980,14 @@
             }
         }
        }
+    
+       protected void makeTurtle() {
+               synchronized(this) {
+                       if(tryTurtle) return;
+                       tryTurtle = true;
+               }
+               node.makeTurtle(RequestSender.this);
+       }

        /**
      * Finish fetching an SSK. We must have received the data, the headers and 
the pubkey by this point.
@@ -1036,7 +1125,7 @@
                        if(prb != null)
                                current |= WAIT_TRANSFERRING_DATA;

-               if(status != NOT_FINISHED)
+               if(status != NOT_FINISHED || sentAbortDownstreamTransfers)
                        current |= WAIT_FINISHED;

                if(current != mask) return current;
@@ -1055,17 +1144,46 @@
        }
     }

+       private static MedianMeanRunningAverage avgTimeTaken = new 
MedianMeanRunningAverage();
+       
+       private static MedianMeanRunningAverage avgTimeTakenTurtle = new 
MedianMeanRunningAverage();
+       
+       private static MedianMeanRunningAverage avgTimeTakenTransfer = new 
MedianMeanRunningAverage();
+       
+       private long transferTime;
+       
     private void finish(int code, PeerNode next, boolean fromOfferedKey) {
        if(logMINOR) Logger.minor(this, "finish("+code+ ')');

+       boolean turtle;
+       
         synchronized(this) {
             status = code;
             notifyAll();
+            turtle = turtleMode;
             if(status == SUCCESS)
                successFrom = next;
         }

         if(status == SUCCESS) {
+               if(key instanceof NodeCHK && transferTime > 0 && logMINOR) {
+                       long timeTaken = System.currentTimeMillis() - startTime;
+                       synchronized(avgTimeTaken) {
+                               if(turtle)
+                                       avgTimeTakenTurtle.report(timeTaken);
+                               else {
+                                       avgTimeTaken.report(timeTaken);
+                               avgTimeTakenTransfer.report(transferTime);
+                               }
+                               if(turtle) {
+                                       if(logMINOR) Logger.minor(this, 
"Successful CHK turtle request took "+timeTaken+" average "+avgTimeTakenTurtle);
+                               } else {
+                                       if(logMINOR) Logger.minor(this, 
"Successful CHK request took "+timeTaken+" average "+avgTimeTaken);
+                               if(logMINOR) Logger.minor(this, "Successful CHK 
request transfer "+transferTime+" average "+avgTimeTakenTransfer);
+                               if(logMINOR) Logger.minor(this, "Search phase: 
median "+(avgTimeTaken.currentValue() - 
avgTimeTakenTransfer.currentValue())+"ms, mean "+(avgTimeTaken.meanValue() - 
avgTimeTakenTransfer.meanValue())+"ms");
+                               }
+                       }
+               }
                if(next != null) {
                        next.onSuccess(false, key instanceof NodeSSK);
                }
@@ -1283,6 +1401,9 @@
                void onCHKTransferBegins();
                /** Should return quickly, allocate a thread if it needs to 
block etc */
                void onRequestSenderFinished(int status);
+               /** Abort downstream transfers (not necessarily upstream ones, 
so not via the PRB).
+                * Should return quickly, allocate a thread if it needs to 
block etc. */
+               void onAbortDownstreamTransfers(int reason, String desc);
        }

        public void addListener(Listener l) {
@@ -1291,10 +1412,13 @@
                boolean reject=false;
                boolean transfer=false;
                boolean sentFinished;
+               boolean sentTransferCancel = false;
                int status;
                synchronized (this) {
                        synchronized (listeners) {
-                               listeners.add(l);
+                               sentTransferCancel = 
sentAbortDownstreamTransfers;
+                               if(!sentTransferCancel)
+                                       listeners.add(l);
                                reject = sentReceivedRejectOverload;
                                transfer = sentCHKTransferBegins;
                                sentFinished = sentRequestSenderFinished;
@@ -1307,6 +1431,8 @@
                        l.onReceivedRejectOverload();
                if (transfer)
                        l.onCHKTransferBegins();
+               if(sentTransferCancel)
+                       
l.onAbortDownstreamTransfers(abortDownstreamTransfersReason, 
abortDownstreamTransfersDesc);
                if (status!=NOT_FINISHED && sentFinished)
                        l.onRequestSenderFinished(status);
        }
@@ -1357,7 +1483,66 @@
                }
        }

+       private boolean sentAbortDownstreamTransfers;
+       private int abortDownstreamTransfersReason;
+       private String abortDownstreamTransfersDesc;
+       
+       private void sendAbortDownstreamTransfers(int reason, String desc) {
+               synchronized (listeners) {
+                       abortDownstreamTransfersReason = reason;
+                       abortDownstreamTransfersDesc = desc;
+                       sentAbortDownstreamTransfers = true;
+                       for (Listener l : listeners) {
+                               try {
+                                       l.onAbortDownstreamTransfers(reason, 
desc);
+                                       
l.onRequestSenderFinished(TRANSFER_FAILED);
+                               } catch (Throwable t) {
+                                       Logger.error(this, "Caught: "+t, t);
+                               }
+                       }
+                       listeners.clear();
+               }
+               synchronized(this) {
+                       notifyAll();
+               }
+       }
+       
        public int getPriority() {
                return NativeThread.HIGH_PRIORITY;
        }
+
+       public void setTurtle() {
+               synchronized(this) {
+                       this.turtleMode = true;
+               }
+               
sendAbortDownstreamTransfers(RetrievalException.GONE_TO_TURTLE_MODE, 
"Turtling");
+               node.getTicker().queueTimedJob(new Runnable() {
+
+                       public void run() {
+                               PeerNode from;
+                               synchronized(RequestSender.this) {
+                                       if(sentBackoffTurtle) return;
+                                       sentBackoffTurtle = true;
+                                       from = transferringFrom;
+                                       if(from == null) return;
+                               }
+                               from.transferFailed("Turtled transfer");
+                       }
+                       
+               }, 30*1000);
+       }
+
+       public PeerNode transferringFrom() {
+               return transferringFrom;
+       }
+
+       public void killTurtle() {
+               prb.abort(RetrievalException.TURTLE_KILLED, "Too many turtles / 
already have turtles for this key");
+               node.failureTable.onFinalFailure(key, transferringFrom(), htl, 
FailureTable.REJECT_TIME, source);
+       }
+
+       public boolean abortedDownstreamTransfers() {
+               return sentAbortDownstreamTransfers;
+       }
+
 }

Copied: branches/db4o/freenet/src/freenet/node/RequestTag.java (from rev 25205, 
trunk/freenet/src/freenet/node/RequestTag.java)
===================================================================
--- branches/db4o/freenet/src/freenet/node/RequestTag.java                      
        (rev 0)
+++ branches/db4o/freenet/src/freenet/node/RequestTag.java      2009-01-22 
18:32:20 UTC (rev 25217)
@@ -0,0 +1,104 @@
+package freenet.node;
+
+import java.lang.ref.WeakReference;
+
+import freenet.io.comm.NotConnectedException;
+import freenet.support.Logger;
+import freenet.support.TimeUtil;
+
+/**
+ * Tag for a request.
+ * @author Matthew Toseland <toad at amphibian.dyndns.org> (0xE43DA450)
+ */
+public class RequestTag extends UIDTag {
+       
+       enum START {
+               ASYNC_GET,
+               LOCAL,
+               REMOTE
+       }
+       
+       final START start;
+       final boolean isSSK;
+       boolean servedFromDatastore;
+       WeakReference<RequestSender> sender;
+       int requestSenderFinishedCode;
+       Throwable handlerThrew;
+       boolean rejected;
+       boolean abortedDownstreamTransfer;
+       int abortedDownstreamReason;
+       String abortedDownstreamDesc;
+       boolean handlerDisconnected;
+
+       public RequestTag(boolean isSSK, START start) {
+               super();
+               this.start = start;
+               this.isSSK = isSSK;
+       }
+
+       public void setRequestSenderFinished(int status) {
+               requestSenderFinishedCode = status;
+       }
+
+       public void setSender(RequestSender rs) {
+               sender = new WeakReference<RequestSender>(rs);
+       }
+
+       public void handlerThrew(Throwable t) {
+               this.handlerThrew = t;
+       }
+
+       public void setServedFromDatastore() {
+               servedFromDatastore = true;
+       }
+
+       public void setRejected() {
+               rejected = true;
+       }
+
+       @Override
+       public void logStillPresent(Long uid) {
+               StringBuffer sb = new StringBuffer();
+               sb.append("Still present after 
").append(TimeUtil.formatTime(age()));
+               sb.append(" : ").append(uid).append(" : start=").append(start);
+               sb.append(" ssk=").append(isSSK).append(" from 
store=").append(servedFromDatastore);
+               if(sender == null) {
+                       sb.append(" sender hasn't been set!");
+               } else {
+                       RequestSender s = sender.get();
+                       if(s == null) {
+                               sb.append(" sender=null");
+                       } else {
+                               sb.append(" sender=").append(s);
+                               sb.append(" status=");
+                               sb.append(s.getStatusString());
+                       }
+               }
+               sb.append(" finishedCode=").append(requestSenderFinishedCode);
+               sb.append(" rejected=").append(rejected);
+               sb.append(" thrown=").append(handlerThrew);
+               if(abortedDownstreamTransfer) {
+                       sb.append(" abortedDownstreamTransfer reason=");
+                       sb.append(abortedDownstreamReason);
+                       sb.append(" desc=");
+                       sb.append(abortedDownstreamDesc);
+               }
+               if(handlerDisconnected)
+                       sb.append(" handlerDisconnected=true");
+               if(handlerThrew != null)
+                       Logger.error(this, sb.toString(), handlerThrew);
+               else
+                       Logger.error(this, sb.toString());
+       }
+
+       public void onAbortDownstreamTransfers(int reason, String desc) {
+               abortedDownstreamTransfer = true;
+               abortedDownstreamReason = reason;
+               abortedDownstreamDesc = desc;
+       }
+
+       public void handlerDisconnected() {
+               handlerDisconnected = true;
+       }
+
+}

Modified: branches/db4o/freenet/src/freenet/node/SSKInsertHandler.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/SSKInsertHandler.java        
2009-01-22 18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/SSKInsertHandler.java        
2009-01-22 18:32:20 UTC (rev 25217)
@@ -45,8 +45,9 @@
     private byte[] data;
     private byte[] headers;
     private boolean canCommit;
+    final InsertTag tag;

-    SSKInsertHandler(NodeSSK key, byte[] data, byte[] headers, short htl, 
PeerNode source, long id, Node node, long startTime) {
+    SSKInsertHandler(NodeSSK key, byte[] data, byte[] headers, short htl, 
PeerNode source, long id, Node node, long startTime, InsertTag tag) {
         this.node = node;
         this.uid = id;
         this.source = source;
@@ -55,6 +56,7 @@
         this.htl = htl;
         this.data = data;
         this.headers = headers;
+        this.tag = tag;
         if(htl <= 0) htl = 1;
         byte[] pubKeyHash = key.getPubKeyHash();
         pubKey = node.getKey(pubKeyHash);
@@ -77,7 +79,7 @@
             Logger.error(this, "Caught "+t, t);
         } finally {
             if(logMINOR) Logger.minor(this, "Exiting InsertHandler.run() for 
"+uid);
-            node.unlockUID(uid, true, true, false, false, false);
+            node.unlockUID(uid, true, true, false, false, false, tag);
         }
     }


Modified: branches/db4o/freenet/src/freenet/node/SessionKey.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/SessionKey.java      2009-01-22 
18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/SessionKey.java      2009-01-22 
18:32:20 UTC (rev 25217)
@@ -18,7 +18,6 @@
         * without changing the session key. */
        final PacketTracker packets;

-       private static boolean logMINOR;
        /** Parent PeerNode */
        public final PeerNode pn;
        /** Cipher to both encrypt outgoing packets with and decrypt

Copied: branches/db4o/freenet/src/freenet/node/UIDTag.java (from rev 25205, 
trunk/freenet/src/freenet/node/UIDTag.java)
===================================================================
--- branches/db4o/freenet/src/freenet/node/UIDTag.java                          
(rev 0)
+++ branches/db4o/freenet/src/freenet/node/UIDTag.java  2009-01-22 18:32:20 UTC 
(rev 25217)
@@ -0,0 +1,23 @@
+package freenet.node;
+
+/**
+ * Base class for tags representing a running request. These store enough 
information
+ * to detect whether they are finished; if they are still in the list, this 
normally
+ * represents a bug.
+ * @author Matthew Toseland <toad at amphibian.dyndns.org> (0xE43DA450)
+ */
+public abstract class UIDTag {
+       
+       final long createdTime;
+       
+       UIDTag() {
+               createdTime = System.currentTimeMillis();
+       }
+
+       public abstract void logStillPresent(Long uid);
+
+       long age() {
+               return System.currentTimeMillis() - createdTime;
+       }
+
+}

Modified: branches/db4o/freenet/src/freenet/node/Version.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/Version.java 2009-01-22 18:08:25 UTC 
(rev 25216)
+++ branches/db4o/freenet/src/freenet/node/Version.java 2009-01-22 18:32:20 UTC 
(rev 25217)
@@ -24,17 +24,17 @@
        public static final String protocolVersion = "1.0";

        /** The build number of the current revision */
-       private static final int buildNumber = 1194;
+       private static final int buildNumber = 1203;

        /** Oldest build of Fred we will talk to */
-       private static final int oldLastGoodBuild = 1193;
-       private static final int newLastGoodBuild = 1194;
+       private static final int oldLastGoodBuild = 1197;
+       private static final int newLastGoodBuild = 1198;
        static final long transitionTime;

        static {
                final Calendar _cal = 
Calendar.getInstance(TimeZone.getTimeZone("GMT"));
                // year, month - 1 (or constant), day, hour, minute, second
-               _cal.set( 2008, Calendar.DECEMBER, 19, 0, 0, 0 );
+               _cal.set( 2009, Calendar.JANUARY, 17, 0, 0, 0 );
                transitionTime = _cal.getTimeInMillis();
        }


Modified: 
branches/db4o/freenet/src/freenet/node/simulator/RealNodeNetworkColoringTest.java
===================================================================
--- 
branches/db4o/freenet/src/freenet/node/simulator/RealNodeNetworkColoringTest.java
   2009-01-22 18:08:25 UTC (rev 25216)
+++ 
branches/db4o/freenet/src/freenet/node/simulator/RealNodeNetworkColoringTest.java
   2009-01-22 18:32:20 UTC (rev 25217)
@@ -178,8 +178,8 @@
                                        rate = 1.0*good/total;
                                general.report(rate);
                                aRate.report(rate);
-                               generalIds.add(new Integer(id));
-                               aIds.add(new Integer(id));
+                               generalIds.add(Integer.valueOf(id));
+                               aIds.add(Integer.valueOf(id));
                        }

                        for (int i=0; i<NUMBER_OF_NODES; i++) {
@@ -195,8 +195,8 @@
                                        rate = 1.0*good/total;
                                general.report(rate);
                                bRate.report(rate);
-                               generalIds.add(new Integer(id));
-                               bIds.add(new Integer(id));
+                               generalIds.add(Integer.valueOf(id));
+                               bIds.add(Integer.valueOf(id));
                        }

                        for (int i=0; i<BRIDGES; i++) {
@@ -211,8 +211,8 @@
                                        rate = 1.0*good/total;
                                general.report(rate);
                                bridgeRate.report(rate);
-                               generalIds.add(new Integer(id));
-                               bridgeIds.add(new Integer(id));
+                               generalIds.add(Integer.valueOf(id));
+                               bridgeIds.add(Integer.valueOf(id));
                        }

                        Logger.error(log, "cycle = "+cycleNumber);

Modified: 
branches/db4o/freenet/src/freenet/node/useralerts/UserAlertManager.java
===================================================================
--- branches/db4o/freenet/src/freenet/node/useralerts/UserAlertManager.java     
2009-01-22 18:08:25 UTC (rev 25216)
+++ branches/db4o/freenet/src/freenet/node/useralerts/UserAlertManager.java     
2009-01-22 18:32:20 UTC (rev 25217)
@@ -285,6 +285,7 @@
                        summaryBox = new HTMLNode("div", "class", "infobox 
infobox-information");
                summaryBox.addChild("div", "class", "infobox-header", 
l10n("alertsTitle"));
                HTMLNode summaryContent = summaryBox.addChild("div", "class", 
"infobox-content", alertSummaryString.toString());
+               summaryContent.addChild("#", " ");
                L10n.addL10nSubstitution(summaryContent, 
"UserAlertManager.alertsOnAlertsPage",
                                new String[] { "link", "/link" },
                                new String[] { "<a href=\"/alerts/\">", "</a>" 
});


Reply via email to