Package freenet.node

Source Code of freenet.node.CHKInsertSender$BackgroundTransfer

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.node;

import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.util.ArrayList;
import java.util.List;

import freenet.io.comm.AsyncMessageCallback;
import freenet.io.comm.ByteCounter;
import freenet.io.comm.DMT;
import freenet.io.comm.DisconnectedException;
import freenet.io.comm.Message;
import freenet.io.comm.MessageFilter;
import freenet.io.comm.NotConnectedException;
import freenet.io.comm.PeerContext;
import freenet.io.comm.SlowAsyncMessageFilterCallback;
import freenet.io.xfer.AbortedException;
import freenet.io.xfer.BlockTransmitter;
import freenet.io.xfer.BlockTransmitter.BlockTransmitterCompletion;
import freenet.io.xfer.PartiallyReceivedBlock;
import freenet.keys.CHKBlock;
import freenet.keys.CHKVerifyException;
import freenet.keys.NodeCHK;
import freenet.support.Logger;
import freenet.support.io.NativeThread;

public final class CHKInsertSender extends BaseSender implements PrioRunnable, AnyInsertSender, ByteCounter {
 
  private class BackgroundTransfer implements PrioRunnable, SlowAsyncMessageFilterCallback {
    private final long uid;
    /** Node we are waiting for response from */
    final PeerNode pn;
    /** We may be sending data to that node */
    BlockTransmitter bt;
    /** Have we received notice of the downstream success
     * or failure of dependant transfers from that node?
     * Includes timing out. */
    boolean receivedCompletionNotice;
    /** Set when we fatally timeout, or when we get a completion other than a timeout. */
    boolean finishedWaiting;

    /** Was the notification of successful transfer? */
    boolean completionSucceeded;
   
    /** Have we completed the immediate transfer? */
    boolean completedTransfer;
    /** Did it succeed? */
    //boolean transferSucceeded;
   
    /** Do we have the InsertReply, RNF or similar completion? If not,
     * there is no point starting to wait for a timeout. */
    boolean gotInsertReply;
    /** Have we started the first wait? We start waiting when we have
     * completed the transfer AND received an InsertReply, RNF or similar. */
    private boolean startedWait;
    /** Has the background transfer been terminated due to not receiving
     * an InsertReply, or due to disconnection etc? */
    private boolean killed;
   
    private final InsertTag thisTag;
   
    BackgroundTransfer(final PeerNode pn, PartiallyReceivedBlock prb, InsertTag thisTag) {
      this.pn = pn;
      this.uid = CHKInsertSender.this.uid;
      this.thisTag = thisTag;
      bt = new BlockTransmitter(node.usm, node.getTicker(), pn, uid, prb, CHKInsertSender.this, BlockTransmitter.NEVER_CASCADE,
          new BlockTransmitterCompletion() {

        @Override
        public void blockTransferFinished(boolean success) {
          if(logMINOR) Logger.minor(this, "Transfer completed: "+success+" for "+this);
          BackgroundTransfer.this.completedTransfer(success);
          // Double-check that the node is still connected. Pointless to wait otherwise.
          if (pn.isConnected() && success) {
            synchronized(backgroundTransfers) {
              if(!gotInsertReply) return;
              if(startedWait) return;
              startedWait = true;
            }
            startWait();
          } else {
            BackgroundTransfer.this.receivedNotice(false, false, false);
            pn.localRejectedOverload("TransferFailedInsert", realTimeFlag);
          }
        }
       
      }, realTimeFlag, node.nodeStats);
    }
   
    /** Start waiting for an acknowledgement or timeout. Caller must ensure
     * that the transfer has succeeded and we have received an RNF, InsertReply
     * or other valid completion. The timeout is relative to that, since up
     * to that point we could still be routing. */
    private void startWait() {
      if(logMINOR) Logger.minor(this, "Waiting for completion notification from "+this);
      //synch-version: this.receivedNotice(waitForReceivedNotification(this));
      //Add ourselves as a listener for the longterm completion message of this transfer, then gracefully exit.
      try {
        node.usm.addAsyncFilter(getNotificationMessageFilter(false), BackgroundTransfer.this, null);
      } catch (DisconnectedException e) {
        // Normal
        if(logMINOR)
          Logger.minor(this, "Disconnected while adding filter");
        BackgroundTransfer.this.completedTransfer(false);
        BackgroundTransfer.this.receivedNotice(false, false, true);
      }
    }
   
    void start() {
      node.executor.execute(this, "CHKInsert-BackgroundTransfer for "+uid+" to "+pn.getPeer());
    }
   
    @Override
    public void run() {
      freenet.support.Logger.OSThread.logPID(this);
      try {
        this.realRun();
      } catch (Throwable t) {
        this.completedTransfer(false);
        this.receivedNotice(false, false, true);
        Logger.error(this, "Caught "+t, t);
      }
    }
   
    private void realRun() {
      bt.sendAsync();
      // REDFLAG: Load limiting:
      // No confirmation that it has finished, and it won't finish immediately on the transfer finishing.
      // So don't try to thisTag.removeRoutingTo(next), just assume it keeps running until the whole insert finishes.
    }
   
    private void completedTransfer(boolean success) {
      synchronized(backgroundTransfers) {
        //transferSucceeded = success; //FIXME Don't used
        completedTransfer = true;
        backgroundTransfers.notifyAll();
      }
      if(!success) {
        setTransferTimedOut();
      }
    }
   
    /** @param timeout Whether this completion is the result of a timeout.
     * @return True if we should wait again, false if we have already received a notice or timed out. */
    private boolean receivedNotice(boolean success, boolean timeout, boolean kill) {
      if(logMINOR) Logger.minor(this, "Received notice: "+success+(timeout ? " (timeout)" : "")+" on "+this);
      boolean noUnlockPeer = false;
      boolean gotFatalTimeout = false;
      synchronized(backgroundTransfers) {
        if(finishedWaiting) {
          if(!(killed || kill))
            Logger.error(this, "Finished waiting already yet receivedNotice("+success+","+timeout+","+kill+")", new Exception("error"));
          return false;
        }
        if(killed) {
          // Do nothing. But do unlock.
        } else if(kill) {
          killed = true;
          finishedWaiting = true;
          receivedCompletionNotice = true;
          completionSucceeded = false;
        } else {
          if (receivedCompletionNotice) {
            // Two stage timeout.
            if(logMINOR) Logger.minor(this, "receivedNotice("+success+"), already had receivedNotice("+completionSucceeded+")");
            if(timeout) {
              // Fatal timeout.
              finishedWaiting = true;
              gotFatalTimeout = true;
            }
          } else {
            // Normal completion.
            completionSucceeded = success;
            receivedCompletionNotice = true;
            if(!timeout) // Any completion mode other than a timeout immediately sets finishedWaiting, because we won't wait any longer.
              finishedWaiting = true;
            else {
              // First timeout but not had second timeout yet.
              // Unlock downstream (below), but will wait here for the peer to fatally timeout.
              // UIDTag will automatically reassign to self when the time comes if we call handlingTimeout() here, and will avoid unnecessarily logging errors.
              // LOCKING: Note that it is safe to call the tag within the lock since we always take the UIDTag lock last.
              thisTag.handlingTimeout(pn);
              noUnlockPeer = true;
            }
          }
        }
        if(!noUnlockPeer)
          startedWait = true; // Prevent further wait's.
      }
      if((!gotFatalTimeout) && (!success)) {
        setTransferTimedOut();
      }
      if(!noUnlockPeer)
        // Downstream (away from originator), we need to stay locked on the peer until the fatal timeout / the delayed notice.
        // Upstream (towards originator), of course, we can unlockHandler() as soon as all the transfers are finished.
        // LOCKING: Do this outside the lock as pn can do heavy stuff in response (new load management).
        pn.noLongerRoutingTo(thisTag, false);
      synchronized(backgroundTransfers) {
        // Avoid "Unlocked handler but still routing to yet not reassigned".
        if(!gotFatalTimeout) {
          backgroundTransfers.notifyAll();
        }
      }
      if(timeout && gotFatalTimeout) {
        Logger.error(this, "Second timeout waiting for final ack from "+pn+" on "+this);
        pn.fatalTimeout(thisTag, false);
        return false;
      }
      return true;
    }
   
    @Override
    public void onMatched(Message m) {
      pn.successNotOverload(realTimeFlag);
      PeerNode pn = (PeerNode) m.getSource();
      // pn cannot be null, because the filters will prevent garbage collection of the nodes
     
      if(this.pn.equals(pn)) {
        boolean anyTimedOut = m.getBoolean(DMT.ANY_TIMED_OUT);
        if(anyTimedOut) {
          CHKInsertSender.this.setTransferTimedOut();
        }
        receivedNotice(!anyTimedOut, false, false);
      } else {
        Logger.error(this, "received completion notice for wrong node: "+pn+" != "+this.pn);
      }     
    }
   
    @Override
    public boolean shouldTimeout() {
      //AFIACS, this will still let the filter timeout, but not call onMatched() twice.
      return finishedWaiting;
    }
   
    private MessageFilter getNotificationMessageFilter(boolean longTimeoutAnyway) {
      return MessageFilter.create().setField(DMT.UID, uid).setType(DMT.FNPInsertTransfersCompleted).setSource(pn).setTimeout(longTimeoutAnyway ? TRANSFER_COMPLETION_ACK_TIMEOUT_BULK : transferCompletionTimeout);
    }

    @Override
    public void onTimeout() {
      /* FIXME: Cascading timeout...
         if this times out, we don't have any time to report to the node of origin the timeout notification (anyTimedOut?).
       */
      // NORMAL priority because it is normally caused by a transfer taking too long downstream, and that doesn't usually indicate a bug.
      Logger.normal(this, "Timed out waiting for a final ack from: "+pn+" on "+this, new Exception("debug"));
      if(receivedNotice(false, true, false)) {
        pn.localRejectedOverload("InsertTimeoutNoFinalAck", realTimeFlag);
        // First timeout. Wait for second timeout.
        try {
          node.usm.addAsyncFilter(getNotificationMessageFilter(true), this, CHKInsertSender.this);
        } catch (DisconnectedException e) {
          // Normal
          if(logMINOR)
            Logger.minor(this, "Disconnected while adding filter after first timeout");
          pn.noLongerRoutingTo(thisTag, false);
        }
      }
    }

    @Override
    public void onDisconnect(PeerContext ctx) {
      Logger.normal(this, "Disconnected "+ctx+" for "+this);
      receivedNotice(true, false, true); // as far as we know
      pn.noLongerRoutingTo(thisTag, false);
    }

    @Override
    public void onRestarted(PeerContext ctx) {
      Logger.normal(this, "Restarted "+ctx+" for "+this);
      receivedNotice(true, false, true);
      pn.noLongerRoutingTo(thisTag, false);
    }

    @Override
    public int getPriority() {
      return NativeThread.HIGH_PRIORITY;
    }
   
    @Override
    public String toString() {
      return super.toString()+":"+uid+":"+pn;
    }

    /** Called when we have received an InsertReply, RouteNotFound or other
     * successful or quasi-successful completion to routing. */
    public void onCompleted() {
      synchronized(backgroundTransfers) {
        if(finishedWaiting) return;
        if(gotInsertReply) return;
        gotInsertReply = true;
        if(!completedTransfer) return;
        if(startedWait) return;
        startedWait = true;
      }
      startWait();
    }

    /** Called when we get a failure, e.g. DataInsertRejected. */
    public void kill() {
      Logger.normal(this, "Killed "+this);
      receivedNotice(false, false, true); // as far as we know
      pn.noLongerRoutingTo(thisTag, false);
    }
  }
 
  CHKInsertSender(NodeCHK myKey, long uid, InsertTag tag, byte[] headers, short htl,
            PeerNode source, Node node, PartiallyReceivedBlock prb, boolean fromStore,
            boolean canWriteClientCache, boolean forkOnCacheable, boolean preferInsert, boolean ignoreLowBackoff, boolean realTimeFlag) {
    super(myKey, realTimeFlag, source, node, htl, uid);
        this.origUID = uid;
        this.origTag = tag;
        this.headers = headers;
        this.prb = prb;
        this.fromStore = fromStore;
        this.startTime = System.currentTimeMillis();
        this.backgroundTransfers = new ArrayList<BackgroundTransfer>();
        this.forkOnCacheable = forkOnCacheable;
        this.preferInsert = preferInsert;
        this.ignoreLowBackoff = ignoreLowBackoff;
        if(realTimeFlag) {
          transferCompletionTimeout = TRANSFER_COMPLETION_ACK_TIMEOUT_REALTIME;
        } else {
          transferCompletionTimeout = TRANSFER_COMPLETION_ACK_TIMEOUT_BULK;
        }
    }

  void start() {
    node.executor.execute(this, "CHKInsertSender for UID "+uid+" on "+node.getDarknetPortNumber()+" at "+System.currentTimeMillis());
  }

  static boolean logMINOR;
  static boolean logDEBUG;
 
  static {
    Logger.registerClass(CHKInsertSender.class);
  }
 
    // Constants
    static final long ACCEPTED_TIMEOUT = SECONDS.toMillis(10);
    static final long TRANSFER_COMPLETION_ACK_TIMEOUT_REALTIME = MINUTES.toMillis(1);
    static final long TRANSFER_COMPLETION_ACK_TIMEOUT_BULK = MINUTES.toMillis(5);

    final long transferCompletionTimeout;
   
    // Basics
    final long origUID;
    final InsertTag origTag;
    private InsertTag forkedRequestTag;
    final byte[] headers; // received BEFORE creation => we handle Accepted elsewhere
    final PartiallyReceivedBlock prb;
    final boolean fromStore;
    private boolean receiveFailed;
    final long startTime;
    private final boolean forkOnCacheable;
    private final boolean preferInsert;
    private final boolean ignoreLowBackoff;

   
    /** List of nodes we are waiting for either a transfer completion
     * notice or a transfer completion from. Also used as a sync object for waiting for transfer completion. */
    private List<BackgroundTransfer> backgroundTransfers;
   
    /** Have all transfers completed and all nodes reported completion status? */
    private boolean allTransfersCompleted;
   
    /** Has a transfer timed out, either directly or downstream? */
    private volatile boolean transferTimedOut;
   
    private int status = -1;
    /** Still running */
    static final int NOT_FINISHED = -1;
    /** Successful insert */
    static final int SUCCESS = 0;
    /** Route not found */
    static final int ROUTE_NOT_FOUND = 1;
    /** Internal error */
    static final int INTERNAL_ERROR = 3;
    /** Timed out waiting for response */
    static final int TIMED_OUT = 4;
    /** Locally Generated a RejectedOverload */
    static final int GENERATED_REJECTED_OVERLOAD = 5;
    /** Could not get off the node at all! */
    static final int ROUTE_REALLY_NOT_FOUND = 6;
    /** Receive failed. Not used internally; only used by CHKInsertHandler. */
    static final int RECEIVE_FAILED = 7;
   
    @Override
  public String toString() {
        return super.toString()+" for "+uid;
    }
   
    @Override
    public void run() {
      freenet.support.Logger.OSThread.logPID(this);
      origTag.startedSender();
        try {
            routeRequests();
        } catch (Throwable t) {
            Logger.error(this, "Caught "+t, t);
        } finally {
          // Always check: we ALWAYS set status, even if receiveFailed.
            int myStatus;
            synchronized (this) {
        myStatus = status;
      }
            if(myStatus == NOT_FINISHED)
              finish(INTERNAL_ERROR, null);
            origTag.finishedSender();
          if(forkedRequestTag != null)
            forkedRequestTag.finishedSender();
        }
    }
   
  static final int MAX_HIGH_HTL_FAILURES = 5;
 
  @Override
    protected void routeRequests() {
       
        PeerNode next = null;
        // While in no-cache mode, we don't decrement HTL on a RejectedLoop or similar, but we only allow a limited number of such failures before RNFing.
        int highHTLFailureCount = 0;
        boolean starting = true;
        while(true) {
          if(failIfReceiveFailed(null, null)) return; // don't need to set status as killed by CHKInsertHandler
           
          if(origTag.shouldStop()) {
            finish(SUCCESS, null);
            return;
          }
         
            /*
             * If we haven't routed to any node yet, decrement according to the source.
             * If we have, decrement according to the node which just failed.
             * Because:
             * 1) If we always decrement according to source then we can be at max or min HTL
             * for a long time while we visit *every* peer node. This is BAD!
             * 2) The node which just failed can be seen as the requestor for our purposes.
             */
            // Decrement at this point so we can DNF immediately on reaching HTL 0.
            boolean canWriteStorePrev = node.canWriteDatastoreInsert(htl);
            if((!starting) && (!canWriteStorePrev)) {
              // We always decrement on starting a sender.
              // However, after that, if our HTL is above the no-cache threshold,
              // we do not want to decrement the HTL for trivial rejections (e.g. RejectedLoop),
              // because we would end up caching data too close to the originator.
              // So allow 5 failures and then RNF.
              if(highHTLFailureCount++ >= MAX_HIGH_HTL_FAILURES) {
                if(logMINOR) Logger.minor(this, "Too many failures at non-cacheable HTL");
                    finish(ROUTE_NOT_FOUND, null);
                    return;
              }
              if(logMINOR) Logger.minor(this, "Allowing failure "+highHTLFailureCount+" htl is still "+htl);
            } else {
                htl = node.decrementHTL(hasForwarded ? next : source, htl);
                if(logMINOR) Logger.minor(this, "Decremented HTL to "+htl);
            }
            starting = false;
            boolean successNow = false;
            boolean noRequest = false;
            synchronized (this) {
              if(htl <= 0) {
                successNow = true;
                // Send an InsertReply back
                noRequest = !hasForwarded;
              }
            }
            if(successNow) {
            if(noRequest)
              origTag.setNotRoutedOnwards();
            finish(SUCCESS, null);
            return;
            }
           
            if( node.canWriteDatastoreInsert(htl) && (!canWriteStorePrev) && forkOnCacheable && forkedRequestTag == null) {
              // FORK! We are now cacheable, and it is quite possible that we have already gone over the ideal sink nodes,
              // in which case if we don't fork we will miss them, and greatly reduce the insert's reachability.
              // So we fork: Create a new UID so we can go over the previous hops again if they happen to be good places to store the data.
             
              // Existing transfers will keep their existing UIDs, since they copied the UID in the constructor.
              // Both local and remote inserts can be forked here: If it has reached this HTL, it means it's already been routed to some nodes.
             
              uid = node.clientCore.makeUID();
        forkedRequestTag = new InsertTag(false, InsertTag.START.REMOTE, source, realTimeFlag, uid, node);
        forkedRequestTag.reassignToSelf();
        forkedRequestTag.startedSender();
        forkedRequestTag.unlockHandler();
        forkedRequestTag.setAccepted();
              Logger.normal(this, "FORKING CHK INSERT "+origUID+" to "+uid);
              nodesRoutedTo.clear();
              node.tracker.lockUID(forkedRequestTag);
            }
           
            // Route it
            // Can backtrack, so only route to nodes closer than we are to target.
            next = node.peers.closerPeer(forkedRequestTag == null ? source : null, nodesRoutedTo, target, true, node.isAdvancedModeEnabled(), -1, null,
              null, htl, ignoreLowBackoff ? Node.LOW_BACKOFF : 0, source == null, realTimeFlag, newLoadManagement);
     
            if(next == null) {
                // Backtrack
            if(!hasForwarded)
              origTag.setNotRoutedOnwards();
                finish(ROUTE_NOT_FOUND, null);
                return;
            }
     
            if(logMINOR) Logger.minor(this, "Routing insert to "+next);
            nodesRoutedTo.add(next);
           
            InsertTag thisTag = forkedRequestTag;
            if(forkedRequestTag == null) thisTag = origTag;
           
      if(failIfReceiveFailed(thisTag, next)) {
        // Need to tell the peer that the DataInsert is not forthcoming.
        // DataInsertRejected is overridden to work both ways.
        try {
          next.sendAsync(DMT.createFNPDataInsertRejected(uid, DMT.DATA_INSERT_REJECTED_RECEIVE_FAILED), null, this);
        } catch (NotConnectedException e) {
          // Ignore
        }
        return;
      }
     
      innerRouteRequests(next, thisTag);
      return;
    }
  }

    private void handleRejectedTimeout(Message msg, PeerNode next) {
      // Some severe lag problem.
      // However it is not fatal because we can be confident now that even if the DataInsert
      // is delivered late, it will not be acted on. I.e. we are certain how many requests
      // are running, which is what fatal timeouts are designed to deal with.
    Logger.warning(this, "Node timed out waiting for our DataInsert (" + msg + " from " + next
        + ") after Accepted in insert - treating as fatal timeout");
    // Terminal overload
    // Try to propagate back to source
    next.localRejectedOverload("AfterInsertAcceptedRejectedTimeout", realTimeFlag);
   
    // We have always started the transfer by the time this is called, so we do NOT need to removeRoutingTo().
    finish(TIMED_OUT, next);
  }

  /** @return True if fatal i.e. we should try another node. */
  private boolean handleRejectedOverload(Message msg, PeerNode next, InsertTag thisTag) {
    // Probably non-fatal, if so, we have time left, can try next one
    if (msg.getBoolean(DMT.IS_LOCAL)) {
      next.localRejectedOverload("ForwardRejectedOverload6", realTimeFlag);
      if(logMINOR) Logger.minor(this,
          "Local RejectedOverload, moving on to next peer");
      // Give up on this one, try another
      return true;
    } else {
      forwardRejectedOverload();
    }
    return false; // Wait for any further response
  }

  private void handleRNF(Message msg, PeerNode next, InsertTag thisTag) {
    if(logMINOR) Logger.minor(this, "Rejected: RNF");
    short newHtl = msg.getShort(DMT.HTL);
    if(newHtl < 0) newHtl = 0;
    synchronized (this) {
      if (htl > newHtl)
        htl = newHtl;           
    }
    // Finished as far as this node is concerned - except for the data transfer, which will continue until it finishes.
    next.successNotOverload(realTimeFlag);
  }

  private void handleDataInsertRejected(Message msg, PeerNode next, InsertTag thisTag) {
    next.successNotOverload(realTimeFlag);
    short reason = msg
        .getShort(DMT.DATA_INSERT_REJECTED_REASON);
    if(logMINOR) Logger.minor(this, "DataInsertRejected: " + reason);
    if (reason == DMT.DATA_INSERT_REJECTED_VERIFY_FAILED) {
      if (fromStore) {
        // That's odd...
        Logger.error(this,"Verify failed on next node "
            + next + " for DataInsert but we were sending from the store!");
      } else {
        try {
          if (!prb.allReceived())
            Logger.error(this,
                "Did not receive all packets but next node says invalid anyway!");
          else {
            // Check the data
            new CHKBlock(prb.getBlock(), headers,
                (NodeCHK) key);
            Logger.error(this,
                "Verify failed on " + next
                + " but data was valid!");
          }
        } catch (CHKVerifyException e) {
          Logger.normal(this,
                  "Verify failed because data was invalid");
        } catch (AbortedException e) {
          onReceiveFailed();
        }
      }
    } else if (reason == DMT.DATA_INSERT_REJECTED_RECEIVE_FAILED) {
      boolean recvFailed;
      synchronized(backgroundTransfers) {
        recvFailed = receiveFailed;
      }
      if (recvFailed) {
        if(logMINOR) Logger.minor(this, "Failed to receive data, so failed to send data");
      } else {
        try {
          if (prb.allReceived()) {
            // Probably caused by transient connectivity problems.
            // Only fatal timeouts warrant ERROR's because they indicate something seriously wrong that didn't result in a disconnection, and because they cause disconnections.
            Logger.warning(this, "Received all data but send failed to " + next);
          } else {
            if (prb.isAborted()) {
              Logger.normal(this, "Send failed: aborted: " + prb.getAbortReason() + ": " + prb.getAbortDescription());
            } else
              Logger.normal(this, "Send failed; have not yet received all data but not aborted: " + next);
          }
        } catch (AbortedException e) {
          onReceiveFailed();
        }
      }
    }
    Logger.error(this, "DataInsert rejected! Reason="
        + DMT.getDataInsertRejectedReason(reason));
  }

  @Override
  protected MessageFilter makeAcceptedRejectedFilter(PeerNode next, long acceptedTimeout, UIDTag tag) {
    // Use the right UID here, in case we fork on cacheable.
    final long uid = tag.uid;
        MessageFilter mfAccepted = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(acceptedTimeout).setType(DMT.FNPAccepted);
        MessageFilter mfRejectedLoop = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(acceptedTimeout).setType(DMT.FNPRejectedLoop);
        MessageFilter mfRejectedOverload = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(acceptedTimeout).setType(DMT.FNPRejectedOverload);
       
        // mfRejectedOverload must be the last thing in the or
        // So its or pointer remains null
        // Otherwise we need to recreate it below
        mfRejectedOverload.clearOr();
        return mfAccepted.or(mfRejectedLoop.or(mfRejectedOverload));
  }

  private static final long TIMEOUT_AFTER_ACCEPTEDREJECTED_TIMEOUT = MINUTES.toMillis(1);

  @Override
  protected void handleAcceptedRejectedTimeout(final PeerNode next, final UIDTag tag) {
    // It could still be running. So the timeout is fatal to the node.
    // This is a WARNING not an ERROR because it's possible that the problem is we simply haven't been able to send the message yet, because we don't use sendSync().
    // FIXME use a callback to rule this out and log an ERROR.
    Logger.warning(this, "Timeout awaiting Accepted/Rejected "+this+" to "+next);
    // Use the right UID here, in case we fork.
    final long uid = tag.uid;
    tag.handlingTimeout(next);
    // The node didn't accept the request. So we don't need to send them the data.
    // However, we do need to wait a bit longer to try to postpone the fatalTimeout().
    // Somewhat intricate logic to try to avoid fatalTimeout() if at all possible.
    MessageFilter mf = makeAcceptedRejectedFilter(next, TIMEOUT_AFTER_ACCEPTEDREJECTED_TIMEOUT, tag);
    try {
      node.usm.addAsyncFilter(mf, new SlowAsyncMessageFilterCallback() {

        @Override
        public void onMatched(Message m) {
          if(m.getSpec() == DMT.FNPRejectedLoop ||
              m.getSpec() == DMT.FNPRejectedOverload) {
            // Ok.
            next.noLongerRoutingTo(tag, false);
          } else {
            assert(m.getSpec() == DMT.FNPAccepted);
            if(logMINOR)
              Logger.minor(this, "Accepted after timeout on "+CHKInsertSender.this+" - will not send DataInsert, waiting for RejectedTimeout");
            // We are not going to send the DataInsert.
            // We have moved on, and we don't want inserts to fork unnecessarily.
            // However, we need to send a DataInsertRejected, or two-stage timeout will happen.
            try {
              next.sendAsync(DMT.createFNPDataInsertRejected(uid, DMT.DATA_INSERT_REJECTED_TIMEOUT_WAITING_FOR_ACCEPTED), new AsyncMessageCallback() {

                @Override
                public void sent() {
                  // Ignore.
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected sent after accepted timeout on "+CHKInsertSender.this);
                }

                @Override
                public void acknowledged() {
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected acknowledged after accepted timeout on "+CHKInsertSender.this);
                  next.noLongerRoutingTo(tag, false);
                }

                @Override
                public void disconnected() {
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected peer disconnected after accepted timeout on "+CHKInsertSender.this);
                  next.noLongerRoutingTo(tag, false);
                }

                @Override
                public void fatalError() {
                  if(logDEBUG) Logger.debug(this, "DataInsertRejected fatal error after accepted timeout on "+CHKInsertSender.this);
                  next.noLongerRoutingTo(tag, false);
                }
               
              }, CHKInsertSender.this);
            } catch (NotConnectedException e) {
              next.noLongerRoutingTo(tag, false);
            }
          }
        }

        @Override
        public boolean shouldTimeout() {
          return false;
        }

        @Override
        public void onTimeout() {
          Logger.error(this, "Fatal: No Accepted/Rejected for "+CHKInsertSender.this);
          next.fatalTimeout(tag, false);
        }

        @Override
        public void onDisconnect(PeerContext ctx) {
          next.noLongerRoutingTo(tag, false);
        }

        @Override
        public void onRestarted(PeerContext ctx) {
          next.noLongerRoutingTo(tag, false);
        }

        @Override
        public int getPriority() {
          return NativeThread.NORM_PRIORITY;
        }
       
      }, this);
    } catch (DisconnectedException e) {
      next.noLongerRoutingTo(tag, false);
    }
  }

  private BackgroundTransfer startBackgroundTransfer(PeerNode node, PartiallyReceivedBlock prb, InsertTag tag) {
    BackgroundTransfer ac = new BackgroundTransfer(node, prb, tag);
    synchronized(backgroundTransfers) {
      backgroundTransfers.add(ac);
      backgroundTransfers.notifyAll();
    }
    ac.start();
    return ac;
  }
 
  private boolean hasForwardedRejectedOverload;
   
    synchronized boolean receivedRejectedOverload() {
      return hasForwardedRejectedOverload;
    }
   
    /** Forward RejectedOverload to the request originator.
     * DO NOT CALL if have a *local* RejectedOverload.
     */
    @Override
    protected synchronized void forwardRejectedOverload() {
      if(hasForwardedRejectedOverload) return;
      hasForwardedRejectedOverload = true;
       notifyAll();
  }
 
  private void setTransferTimedOut() {
    synchronized(this) {
      if(!transferTimedOut) {
        transferTimedOut = true;
        notifyAll();
      }
    }
  }
   
    /**
     * Finish the insert process. Will set status, wait for underlings to complete, and report success
     * if appropriate.
     * @param code The status code to set.
     * @param next The node we successfully inserted to.
     */
    private void finish(int code, PeerNode next) {
      if(logMINOR) Logger.minor(this, "Finished: "+code+" on "+this, new Exception("debug"));
    
      // If there is an InsertReply, it always happens before the transfer completion notice.
      // So we do NOT need to removeRoutingTo().
     
        synchronized(this) {
          if(allTransfersCompleted) return; // Already called. Doesn't prevent race condition resulting in the next bit running but that's not really a problem.
          if((code == ROUTE_NOT_FOUND) && !hasForwarded)
            code = ROUTE_REALLY_NOT_FOUND;

          if(status != NOT_FINISHED) {
            if(status == RECEIVE_FAILED) {
              if(code == SUCCESS)
                Logger.error(this, "Request succeeded despite receive failed?! on "+this);
            } else if(status != TIMED_OUT)
              throw new IllegalStateException("finish() called with "+code+" when was already "+status);
          } else {
                status = code;
          }
         
          notifyAll();
          if(logMINOR) Logger.minor(this, "Set status code: "+getStatusString()+" on "+uid);
        }
   
        boolean failedRecv = false; // receiveFailed is protected by backgroundTransfers but status by this
        // Now wait for transfers, or for downstream transfer notifications.
        // Note that even the data receive may not have completed by this point.
        boolean mustWait = false;
    synchronized(backgroundTransfers) {
      if (backgroundTransfers.isEmpty()) {
        if(logMINOR) Logger.minor(this, "No background transfers");
        failedRecv = receiveFailed;
      } else {
        mustWait = true;
      }
    }
    if(mustWait) {
      waitForBackgroundTransferCompletions();
      synchronized(backgroundTransfers) {
        failedRecv = receiveFailed;
      }
    }
       
    synchronized(this) {
      // waitForBackgroundTransferCompletions() may have already set it.
      if(!allTransfersCompleted) {
        if(failedRecv)
          status = RECEIVE_FAILED;
        allTransfersCompleted = true;
        notifyAll();
      }
    }
         
        if(status == SUCCESS && next != null)
          next.onSuccess(true, false);
       
        if(logMINOR) Logger.minor(this, "Returning from finish()");
    }

    @Override
    public synchronized int getStatus() {
        return status;
    }
   
    @Override
    public synchronized short getHTL() {
        return htl;
    }
   
    public boolean failIfReceiveFailed(InsertTag tag, PeerNode next) {
      synchronized(backgroundTransfers) {
        if(!receiveFailed) return false;
      }
      if(logMINOR) Logger.minor(this, "Failing because receive failed on "+this);
      if(tag != null && next != null) {
         next.noLongerRoutingTo(tag, false);
      }
      return true;
    }

    /**
     * Called by CHKInsertHandler to notify that the receive has
     * failed.
     */
    public void onReceiveFailed() {
      if(logMINOR) Logger.minor(this, "Receive failed on "+this);
      synchronized(backgroundTransfers) {
        receiveFailed = true;
        backgroundTransfers.notifyAll();
        // Locking is safe as UIDTag always taken last.
        for(BackgroundTransfer t : backgroundTransfers)
          t.thisTag.handlingTimeout(t.pn);
      }
      // Set status immediately.
      // The code (e.g. waitForStatus()) relies on a status eventually being set,
      // so we may as well set it here. The alternative is to set it in realRun()
      // when we notice that receiveFailed = true.
      synchronized(this) {
        status = RECEIVE_FAILED;
        allTransfersCompleted = true;
        notifyAll();
      }
      // Do not call finish(), that can only be called on the main thread and it will block.
    }

    /**
     * @return The current status as a string
     */
    @Override
    public synchronized String getStatusString() {
        if(status == SUCCESS)
            return "SUCCESS";
        if(status == ROUTE_NOT_FOUND)
            return "ROUTE NOT FOUND";
        if(status == NOT_FINISHED)
            return "NOT FINISHED";
        if(status == INTERNAL_ERROR)
          return "INTERNAL ERROR";
        if(status == TIMED_OUT)
          return "TIMED OUT";
        if(status == GENERATED_REJECTED_OVERLOAD)
          return "GENERATED REJECTED OVERLOAD";
        if(status == ROUTE_REALLY_NOT_FOUND)
          return "ROUTE REALLY NOT FOUND";
        return "UNKNOWN STATUS CODE: "+status;
    }

  @Override
  public synchronized boolean sentRequest() {
    return hasForwarded;
  }
   
    private void waitForBackgroundTransferCompletions() {
      try {
        freenet.support.Logger.OSThread.logPID(this);
        if(logMINOR) Logger.minor(this, "Waiting for background transfer completions: "+this);
       
        // We must presently be at such a stage that no more background transfers will be added.
       
        BackgroundTransfer[] transfers;
        synchronized(backgroundTransfers) {
          transfers = new BackgroundTransfer[backgroundTransfers.size()];
          transfers = backgroundTransfers.toArray(transfers);
        }
       
        // Wait for the outgoing transfers to complete.
        if(!waitForBackgroundTransfers(transfers)) {
          setTransferTimedOut();
          return;
        }
      } finally {
        synchronized(CHKInsertSender.this) {
          allTransfersCompleted = true;
          CHKInsertSender.this.notifyAll();
        }
      }
    }

    /**
     * Block until all transfers have reached a final-terminal state (success/failure). On success this means that a
     * successful 'received-notification' has been received.
     * @return True if all background transfers were successful.
     */
    private boolean waitForBackgroundTransfers(BackgroundTransfer[] transfers) {
      long start = System.currentTimeMillis();
      // Generous deadline so we catch bugs more obviously
      long deadline = start + transferCompletionTimeout * 3;
      // MAYBE all done
      while(true) {
        if(System.currentTimeMillis() > deadline) {
          // NORMAL priority because it is normally caused by a transfer taking too long downstream, and that doesn't usually indicate a bug.
          Logger.normal(this, "Timed out waiting for background transfers! Probably caused by async filter not getting a timeout notification! DEBUG ME!");
          return false;
        }
        //If we want to be sure to exit as-soon-as the transfers are done, then we must hold the lock while we check.
        synchronized(backgroundTransfers) {
          if(receiveFailed) return false;
         
          boolean noneRouteable = true;
          boolean completedTransfers = true;
          boolean completedNotifications = true;
          boolean someFailed = false;
          for(BackgroundTransfer transfer: transfers) {
            if(!transfer.pn.isRoutable()) {
              if(logMINOR)
                Logger.minor(this, "Ignoring transfer to "+transfer.pn+" for "+this+" as not routable");
              continue;
            }
            noneRouteable = false;
            if(!transfer.completedTransfer) {
              if(logMINOR)
                Logger.minor(this, "Waiting for transfer completion to "+transfer.pn+" : "+transfer);
              //must wait
              completedTransfers = false;
              break;
            }
            if (!transfer.receivedCompletionNotice) {
              if(logMINOR)
                Logger.minor(this, "Waiting for completion notice from "+transfer.pn+" : "+transfer);
              //must wait
              completedNotifications = false;
              break;
            }
            if (!transfer.completionSucceeded)
              someFailed = true;
          }
          if(noneRouteable) return false;
          if(completedTransfers && completedNotifications) return !someFailed;
         
          if(logMINOR) Logger.minor(this, "Waiting: transfer completion=" + completedTransfers + " notification="+completedNotifications);
          try {
            backgroundTransfers.wait(SECONDS.toMillis(100));
          } catch (InterruptedException e) {
            // Ignore
          }
        }       
      }
    }


  public synchronized boolean completed() {
    return allTransfersCompleted;
  }

  /** Block until status has been set to something other than NOT_FINISHED */
  public synchronized void waitForStatus() {
    while(status == NOT_FINISHED) {
      try {
        CHKInsertSender.this.wait(SECONDS.toMillis(100));
      } catch (InterruptedException e) {
        // Ignore
      }
    }
  }

  public boolean anyTransfersFailed() {
    return transferTimedOut;
  }

  public byte[] getPubkeyHash() {
    return headers;
  }

  public byte[] getHeaders() {
    return headers;
  }

  @Override
  public long getUID() {
    return uid;
  }

  private final Object totalBytesSync = new Object();
  private int totalBytesSent;
 
  @Override
  public void sentBytes(int x) {
    synchronized(totalBytesSync) {
      totalBytesSent += x;
    }
    node.nodeStats.insertSentBytes(false, x);
  }
 
  public int getTotalSentBytes() {
    synchronized(totalBytesSync) {
      return totalBytesSent;
    }
  }
 
  private int totalBytesReceived;
 
  @Override
  public void receivedBytes(int x) {
    synchronized(totalBytesSync) {
      totalBytesReceived += x;
    }
    node.nodeStats.insertReceivedBytes(false, x);
  }
 
  public int getTotalReceivedBytes() {
    synchronized(totalBytesSync) {
      return totalBytesReceived;
    }
  }

  @Override
  public void sentPayload(int x) {
    node.sentPayload(x);
    node.nodeStats.insertSentBytes(false, -x);
  }

  public boolean failedReceive() {
    return receiveFailed;
  }

  public boolean startedSendingData() {
    synchronized(backgroundTransfers) {
      return !backgroundTransfers.isEmpty();
    }
  }

  @Override
  public int getPriority() {
    return NativeThread.HIGH_PRIORITY;
  }

  public PeerNode[] getRoutedTo() {
    return this.nodesRoutedTo.toArray(new PeerNode[nodesRoutedTo.size()]);
  }

  @Override
  protected Message createDataRequest() {
        Message req;
       
        req = DMT.createFNPInsertRequest(uid, htl, key);
        if(forkOnCacheable != Node.FORK_ON_CACHEABLE_DEFAULT) {
          req.addSubMessage(DMT.createFNPSubInsertForkControl(forkOnCacheable));
        }
        if(ignoreLowBackoff != Node.IGNORE_LOW_BACKOFF_DEFAULT) {
          req.addSubMessage(DMT.createFNPSubInsertIgnoreLowBackoff(ignoreLowBackoff));
        }
        if(preferInsert != Node.PREFER_INSERT_DEFAULT) {
          req.addSubMessage(DMT.createFNPSubInsertPreferInsert(preferInsert));
        }
      req.addSubMessage(DMT.createFNPRealTimeFlag(realTimeFlag));
       
      return req;
  }

  @Override
  protected long getAcceptedTimeout() {
    return ACCEPTED_TIMEOUT;
  }

  @Override
  protected void timedOutWhileWaiting(double load) {
    htl -= (short)Math.max(0, hopsForFatalTimeoutWaitingForPeer());
    if(htl < 0) htl = 0;
        // Backtrack, i.e. RNF.
    if(!hasForwarded)
      origTag.setNotRoutedOnwards();
        finish(ROUTE_NOT_FOUND, null);
  }

  @Override
  protected void onAccepted(PeerNode next) {
        // Send them the data.
        // Which might be the new data resulting from a collision...

        Message dataInsert;
        dataInsert = DMT.createFNPDataInsert(uid, headers);
        /** What are we waiting for now??:
         * - FNPRouteNotFound - couldn't exhaust HTL, but send us the
         *   data anyway please
         * - FNPInsertReply - used up all HTL, yay
         * - FNPRejectOverload - propagating an overload error :(
         * - FNPRejectTimeout - we took too long to send the DataInsert
         * - FNPDataInsertRejected - the insert was invalid
         */
       
        int searchTimeout = calculateTimeout(htl);
        MessageFilter mfInsertReply = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPInsertReply);
        MessageFilter mfRejectedOverload = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRejectedOverload);
        MessageFilter mfRouteNotFound = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRouteNotFound);
        MessageFilter mfDataInsertRejected = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPDataInsertRejected);
        MessageFilter mfTimeout = MessageFilter.create().setSource(next).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRejectedTimeout);
       
        MessageFilter mf = mfInsertReply.or(mfRouteNotFound.or(mfDataInsertRejected.or(mfTimeout.or(mfRejectedOverload))));

        InsertTag thisTag = forkedRequestTag;
        if(forkedRequestTag == null) thisTag = origTag;
       
        if(logMINOR) Logger.minor(this, "Sending DataInsert");
        try {
      next.sendSync(dataInsert, this, realTimeFlag);
    } catch (NotConnectedException e1) {
      if(logMINOR) Logger.minor(this, "Not connected sending DataInsert: "+next+" for "+uid);
      next.noLongerRoutingTo(thisTag, false);
      routeRequests();
      return;
    } catch (SyncSendWaitedTooLongException e) {
      Logger.error(this, "Unable to send "+dataInsert+" to "+next+" in a reasonable time");
      // Other side will fail. No need to do anything.
      next.noLongerRoutingTo(thisTag, false);
      routeRequests();
      return;
    }

    if(logMINOR) Logger.minor(this, "Sending data");
    final BackgroundTransfer transfer =
      startBackgroundTransfer(next, prb, thisTag);
   
    // Once the transfer has started, we only unlock the tag after the transfer completes (successfully or not).
   
        while (true) {

          Message msg;
         
      if(failIfReceiveFailed(thisTag, next)) {
        // The transfer has started, it will be cancelled.
        transfer.onCompleted();
        return;
      }
     
      try {
        msg = node.usm.waitFor(mf, this);
      } catch (DisconnectedException e) {
        Logger.normal(this, "Disconnected from " + next
            + " while waiting for InsertReply on " + this);
        transfer.onDisconnect(next);
        break;
      }
      if(failIfReceiveFailed(thisTag, next)) {
        // The transfer has started, it will be cancelled.
        transfer.onCompleted();
        return;
      }
     
      if (msg == null) {
       
        Logger.warning(this, "Timeout on insert "+this+" to "+next);
       
        // First timeout.
        // Could be caused by the next node, or could be caused downstream.
        next.localRejectedOverload("AfterInsertAcceptedTimeout2", realTimeFlag);
        forwardRejectedOverload();

        synchronized(this) {
          status = TIMED_OUT;
          notifyAll();
        }
       
        // Wait for the second timeout off-thread.
        // FIXME wait asynchronously.
       
        final InsertTag tag = thisTag;
        final PeerNode waitingFor = next;
        final short htl = this.htl;
       
        Runnable r = new Runnable() {

          @Override
          public void run() {
            // We do not need to unlock the tag here.
            // That will happen in the BackgroundTransfer, which has already started.
           
            // FIXME factor out
                int searchTimeout = calculateTimeout(htl);
                    MessageFilter mfInsertReply = MessageFilter.create().setSource(waitingFor).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPInsertReply);
                    MessageFilter mfRejectedOverload = MessageFilter.create().setSource(waitingFor).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRejectedOverload);
                    MessageFilter mfRouteNotFound = MessageFilter.create().setSource(waitingFor).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRouteNotFound);
                    MessageFilter mfDataInsertRejected = MessageFilter.create().setSource(waitingFor).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPDataInsertRejected);
                    MessageFilter mfTimeout = MessageFilter.create().setSource(waitingFor).setField(DMT.UID, uid).setTimeout(searchTimeout).setType(DMT.FNPRejectedTimeout);
                   
                    MessageFilter mf = mfInsertReply.or(mfRouteNotFound.or(mfDataInsertRejected.or(mfTimeout.or(mfRejectedOverload))));

                  while (true) {
                   
                    Message msg;

                if(failIfReceiveFailed(tag, waitingFor)) {
                  transfer.onCompleted();
                  return;
                }
             
              try {
                msg = node.usm.waitFor(mf, CHKInsertSender.this);
              } catch (DisconnectedException e) {
                Logger.normal(this, "Disconnected from " + waitingFor
                    + " while waiting for InsertReply on " + CHKInsertSender.this);
                transfer.onDisconnect(waitingFor);
                return;
              }
             
                if(failIfReceiveFailed(tag, waitingFor)) {
                  transfer.onCompleted();
                  return;
                }
             
              if(msg == null) {
                // Second timeout.
                // Definitely caused by the next node, fatal.
                Logger.error(this, "Got second (local) timeout on "+CHKInsertSender.this+" from "+waitingFor);
                  transfer.onCompleted();
                waitingFor.fatalTimeout();
                return;
              }
             
              if (msg.getSpec() == DMT.FNPRejectedTimeout) {
                // Next node timed out awaiting our DataInsert.
                // But we already sent it, so something is wrong. :(
                handleRejectedTimeout(msg, waitingFor);
                transfer.kill();
                return;
              }

              if (msg.getSpec() == DMT.FNPRejectedOverload) {
                if(handleRejectedOverload(msg, waitingFor, tag)) {
                  // Already set the status, and handle... will have unlocked the next node, so no need to call finished().
                  transfer.onCompleted();
                  return; // Don't try another node.
                }
                else continue;
              }

              if (msg.getSpec() == DMT.FNPRouteNotFound) {
                transfer.onCompleted();
                return; // Don't try another node.
              }
             
              if (msg.getSpec() == DMT.FNPDataInsertRejected) {
                handleDataInsertRejected(msg, waitingFor, tag);
                transfer.kill();
                return; // Don't try another node.
              }
             
              if (msg.getSpec() != DMT.FNPInsertReply) {
                Logger.error(this, "Unknown reply: " + msg);
                transfer.onCompleted();
                return;
              } else {
                // Our task is complete, one node (quite deep), has accepted the insert.
                // The request will not be routed to any other nodes, this is where the data *should* be.
                // We will removeRoutingTo() after the node has sent the transfer completion notice, which never happens before the InsertReply.
                transfer.onCompleted();
                return;
              }
                  }
          }
         
        };
       
        // Wait for the timeout off-thread.
        node.executor.execute(r);
        // Meanwhile, finish() to update allTransfersCompleted and hence allow the CHKInsertHandler to send the message downstream.
        // We have already set the status code, this is necessary in order to avoid race conditions.
        // However since it is set to TIMED_OUT, we are allowed to set it again.
        finish(TIMED_OUT, next);
        return;
      }

      if (msg.getSpec() == DMT.FNPRejectedTimeout) {
        // Next node timed out awaiting our DataInsert.
        // But we already sent it, so something is wrong. :(
        transfer.kill();
        handleRejectedTimeout(msg, next);
        return;
      }

      if (msg.getSpec() == DMT.FNPRejectedOverload) {
        if(handleRejectedOverload(msg, next, thisTag)) {
          // We have had an Accepted. This happens on a timeout downstream.
          // They will complete it (finish()), so we need to wait for a transfer completion.
          // FIXME it might be less confusing and therefore less likely to cause problems
          // if we had a different message sent post-accept???
          transfer.onCompleted();
          break;
        }
        else continue;
      }

      if (msg.getSpec() == DMT.FNPRouteNotFound) {
        //RNF means that the HTL was not exhausted, but that the data will still be stored.
        handleRNF(msg, next, thisTag);
        transfer.onCompleted();
        break;
      }

      //Can occur after reception of the entire chk block
      if (msg.getSpec() == DMT.FNPDataInsertRejected) {
        handleDataInsertRejected(msg, next, thisTag);
        transfer.kill();
        break;
      }
     
      if (msg.getSpec() != DMT.FNPInsertReply) {
        Logger.error(this, "Unknown reply: " + msg);
        transfer.onCompleted();
        finish(INTERNAL_ERROR, next);
        return;
      } else {
        transfer.onCompleted();
        // Our task is complete, one node (quite deep), has accepted the insert.
        // The request will not be routed to any other nodes, this is where the data *should* be.
        // We will removeRoutingTo() after the node has sent the transfer completion notice, which never happens before the InsertReply.
        finish(SUCCESS, next);
        return;
      }
    }
    routeRequests();
  }

  @Override
  protected boolean isInsert() {
    return true;
  }

  @Override
  protected PeerNode sourceForRouting() {
    if(forkedRequestTag != null) return null;
    return source;
  }
 
  @Override
  protected long ignoreLowBackoff() {
    return ignoreLowBackoff ? Node.LOW_BACKOFF : 0;
  }

}
TOP

Related Classes of freenet.node.CHKInsertSender$BackgroundTransfer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.