Package freenet.io.xfer

Source Code of freenet.io.xfer.BlockTransmitter$Future

/*
* Dijjer - A Peer to Peer HTTP Cache
* Copyright (C) 2004,2005 Change.Tv, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package freenet.io.xfer;

import java.util.HashSet;
import java.util.Deque;

import freenet.io.comm.AsyncMessageCallback;
import freenet.io.comm.AsyncMessageFilterCallback;
import freenet.io.comm.ByteCounter;
import freenet.io.comm.DMT;
import freenet.io.comm.DisconnectedException;
import freenet.io.comm.Message;
import freenet.io.comm.MessageCore;
import freenet.io.comm.MessageFilter;
import freenet.io.comm.NotConnectedException;
import freenet.io.comm.PeerContext;
import freenet.io.comm.RetrievalException;
import freenet.node.MessageItem;
import freenet.io.comm.SlowAsyncMessageFilterCallback;
import freenet.node.PrioRunnable;
import freenet.support.BitArray;
import freenet.support.Executor;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Ticker;
import freenet.support.TimeUtil;
import freenet.support.Logger.LogLevel;
import freenet.support.io.NativeThread;
import freenet.support.math.MedianMeanRunningAverage;

/**
* @author ian
*
* Given a PartiallyReceivedBlock retransmit to another node (to be received by BlockReceiver).
* Since a PRB can be concurrently transmitted to many peers NOWHERE in this class is prb.abort() to be called.
*
* SECURITY: We must keep sending the data even if the inter-block interval becomes too
* large for the receiver to be able to accept the data. Otherwise a malicious node can
* use much more bandwidth on our input and upstream nodes than he expends himself, simply
* by doing lots of requests and only accepting a few bytes per second worth of packets.
* Obviously if such situations arise naturally they should be handled via load limiting -
* either the originator itself with an accurate bandwidth limit, or the packets-in-flight
* limit.
*/
public class BlockTransmitter {

  private static volatile boolean logMINOR;

  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
      }
    });
  }

  public static final int SEND_TIMEOUT = 60000;
 
  final MessageCore _usm;
  final PeerContext _destination;
  private boolean _sentSendAborted;
  final long _uid;
  private final boolean realTime;
  final PartiallyReceivedBlock _prb;
  private Deque<Integer> _unsent;
  private BlockSenderJob _senderThread = new BlockSenderJob();
  private BitArray _sentPackets;
  private long timeAllSent = -1;
  final ByteCounter _ctr;
  final int PACKET_SIZE;
  private final ReceiverAbortHandler abortHandler;
  private HashSet<MessageItem> itemsPending = new HashSet<MessageItem>();
 
  private final Ticker _ticker;
  private final Executor _executor;
  private final BlockTransmitterCompletion _callback;
 
  public interface BlockTimeCallback {
    public void blockTime(long interval, boolean realtime);
  }
 
  private final BlockTimeCallback blockTimeCallback;

  /** Have we received a completion acknowledgement from the other side - either a
   * sendAborted or allReceived? */
  private boolean _receivedSendCompletion;
  /** Was it an allReceived? */
  private boolean _receivedSendSuccess;
  /** Have we completed i.e. called the callback? */
  private boolean _completed;
  /** Have we failed e.g. due to PRB abort, disconnection? */
  private boolean _failed;
 
  static int runningBlockTransmits = 0;
 
  class BlockSenderJob implements PrioRunnable {
   
    private boolean running = false;
   
    @Override
    public void run() {
      synchronized(this) {
        if(running) return;
        running = true;
      }
      try {
        while(true) {
          int packetNo = -1;
          BitArray copy;
          synchronized(_senderThread) {
            if(_failed || _receivedSendCompletion || _completed) return;
            if(_unsent.size() == 0) {
              // Wait for PRB callback to tell us we have more packets.
              return;
            }
            else {
              packetNo = _unsent.removeFirst();
              if(_sentPackets.bitAt(packetNo)) {
                Logger.error(this, "Already sent packet in run(): "+packetNo+" for "+this+" unsent is "+_unsent+" sent is "+_sentPackets, new Exception("error"));
                continue;
              }
            }
            copy = _sentPackets.copy();
            _sentPackets.setBit(packetNo, true);
          }
          if(!innerRun(packetNo, copy)) return;
        }
      } finally {
        synchronized(this) {
          running = false;
        }
      }
    }
   
    public void schedule() {
      if(_failed || _receivedSendCompletion || _completed) {
        if(logMINOR) Logger.minor(this, "Not scheduling for "+_uid+" to "+_destination+" :"+
            (_failed ? "(failed) " : "") + (_receivedSendCompletion ? "(receivedSendCompletion) " : "") + (_completed ? "(completed) " : ""));
        return;
      }
      _executor.execute(this, "BlockTransmitter block sender for "+_uid+" to "+_destination);
    }

    /** @return True . */
    private boolean innerRun(int packetNo, BitArray copied) {
      try {
        Message msg = DMT.createPacketTransmit(_uid, packetNo, copied, _prb.getPacket(packetNo), realTime);
        MyAsyncMessageCallback cb = new MyAsyncMessageCallback();
        MessageItem item;
        // Everything is throttled.
        item = _destination.sendAsync(msg, cb, _ctr);
        synchronized(itemsPending) {
          itemsPending.add(item);
        }
      } catch (NotConnectedException e) {
        onDisconnect();
        return false;
      } catch (AbortedException e) {
        Logger.normal(this, "Terminating send due to abort: "+e);
        // The PRB callback will deal with this.
        return false;
      }
      boolean success = false;
      boolean complete = false;
      synchronized (_senderThread) {
        if(_unsent.size() == 0 && getNumSent() == _prb._packets) {
          //No unsent packets, no unreceived packets
          sendAllSentNotification();
          if(maybeAllSent()) {
            if(maybeComplete()) {
              complete = true;
              success = _receivedSendSuccess;
            } else return false;
          } else {
            return false;
          }
        }
      }
      if(complete) {
        callCallback(success);
        return false; // No more blocks to send.
      }
      return true; // More blocks to send.
    }

    @Override
    public int getPriority() {
      return NativeThread.HIGH_PRIORITY;
    }
   
  }
 
  public BlockTransmitter(MessageCore usm, Ticker ticker, PeerContext destination, long uid, PartiallyReceivedBlock source, ByteCounter ctr, ReceiverAbortHandler abortHandler, BlockTransmitterCompletion callback, boolean realTime, BlockTimeCallback blockTimes) {
    this.realTime = realTime;
    _ticker = ticker;
    _executor = _ticker.getExecutor();
    _callback = callback;
    this.abortHandler = abortHandler;
    _usm = usm;
    _destination = destination;
    _uid = uid;
    _prb = source;
    _ctr = ctr;
    if(_ctr == null) throw new NullPointerException();
    PACKET_SIZE = DMT.packetTransmitSize(_prb._packetSize, _prb._packets);
    try {
      _sentPackets = new BitArray(_prb.getNumPackets());
    } catch (AbortedException e) {
      Logger.error(this, "Aborted during setup");
      // Will throw on running
    }
    this.blockTimeCallback = blockTimes;
    if(logMINOR) Logger.minor(this, "Starting block transmit for "+uid+" to "+destination.shortToString()+" realtime="+realTime);
  }

  private Runnable timeoutJob;
 
  public void scheduleTimeoutAfterBlockSends() {
    synchronized(_senderThread) {
      if(_receivedSendCompletion) return;
      if(timeoutJob != null) return;
      if(logMINOR) Logger.minor(this, "Scheduling timeout on "+this);
      timeoutJob = new PrioRunnable() {
       
        @Override
        public void run() {
          String timeString;
          String abortReason;
          Future fail;
          synchronized(_senderThread) {
            if(_completed) return;
            boolean hadSendCompletion = _receivedSendCompletion;
            if(!_receivedSendCompletion) {
              _receivedSendCompletion = true;
              _receivedSendSuccess = false;
            }
            //SEND_TIMEOUT (one minute) after all packets have been transmitted, terminate the send.
            if(_failed) {
              // Already failed, we were just waiting for the acknowledgement sendAborted.
              if(!hadSendCompletion) {
                Logger.warning(this, "Terminating send after failure on "+this);
                abortReason = "Already failed and no acknowledgement";
              } else {
                // Waiting for transfers maybe???
                if(logMINOR) Logger.minor(this, "Trying to terminate send after timeout");
                abortReason = "Already failed";
              }
            } else {
              timeString=TimeUtil.formatTime((System.currentTimeMillis() - timeAllSent), 2, true);
              Logger.warning(this, "Terminating send "+_uid+" to "+_destination+" from "+_destination.getSocketHandler()+" as we haven't heard from receiver in "+timeString+ '.');
              abortReason = "Haven't heard from you (receiver) in "+timeString;
            }
            fail = maybeFail(RetrievalException.RECEIVER_DIED, abortReason);
          }
          fail.execute();
        }
       
        @Override
        public int getPriority() {
          return NativeThread.NORM_PRIORITY;
        }
       
      };
      _ticker.queueTimedJob(timeoutJob, "Timeout for "+this, SEND_TIMEOUT, false, false);
    }
  }

  /** LOCKING: Must be called with _senderThread held.
   * @return True if everything has been sent and we are now just waiting for an
   * acknowledgement or timeout from the other side. */
  public boolean maybeAllSent() {
    if(blockSendsPending == 0 && _unsent.size() == 0 && getNumSent() == _prb._packets) {
      timeAllSent = System.currentTimeMillis();
      if(logMINOR)
        Logger.minor(this, "Sent all blocks, none unsent on "+this);
      _senderThread.notifyAll();
      return true;
    }
    if(blockSendsPending == 0 && _failed) {
      timeAllSent = System.currentTimeMillis();
      if(logMINOR)
        Logger.minor(this, "Sent blocks and failed on "+this);
      return true;
    }
    if(logMINOR) Logger.minor(this, "maybeAllSent: block sends pending = "+blockSendsPending+" unsent = "+_unsent.size()+" sent = "+getNumSent()+" on "+this);
    return false;
  }

  /** Complete? maybeAllSent() must have already returned true. This method checks
   * _sendCompleted and then uses _completed to complete only once. LOCKING: Must be
   * called with _senderThread held.
   * Caller must call the callback then call cleanup() outside the lock if this returns true. */
  public boolean maybeComplete() {
    if(!_receivedSendCompletion) {
      if(logMINOR) Logger.minor(this, "maybeComplete() not completing because send not completed on "+this);
      // All the block sends have completed, wait for the other side to acknowledge or timeout.
      scheduleTimeoutAfterBlockSends();
      return false;
    }
    if(_completed) {
      if(logMINOR) Logger.minor(this, "maybeComplete() already completed on "+this);
      return false;
    }
    if(logMINOR) Logger.minor(this, "maybeComplete() completing on "+this);
    _completed = true;
    decRunningBlockTransmits();
    return true;
  }
 
  interface Future {
    void execute();
  }
 
  private final Future nullFuture = new Future() {

    @Override
    public void execute() {
      // Do nothing.
    }
   
  };
 
  /** Only fail once. Called on a drastic failure e.g. disconnection. Unless we are sure
   * that we don't need to (e.g. on disconnection), the caller must call prepareSendAborted
   * afterwards, and if that returns true, send the sendAborted via innerSendAborted.
   * LOCKING: Must be called inside the _senderThread lock.
   * @return A Future which the caller must execute() outside the lock. */
  public Future maybeFail(final int reason, final String description) {
    if(_completed) {
      if(logMINOR) Logger.minor(this, "maybeFail() already completed on "+this);
      return nullFuture;
    }
    _failed = true;
    if(!_receivedSendCompletion) {
      // Don't actually timeout until after we have an acknowledgement of the transfer cancel.
      // This is important for keeping track of how many transfers are actually running, which will be important for load management later on.
      // The caller will immediately call prepareSendAbort() then innerSendAborted().
      if(logMINOR) Logger.minor(this, "maybeFail() waiting for acknowledgement on "+this);
      if(_sentSendAborted) {
        scheduleTimeoutAfterBlockSends();
        return nullFuture; // Do nothing, waiting for timeout.
      } else {
        _sentSendAborted = true;
        // Send the aborted, then wait.
        return new Future() {

          @Override
          public void execute() {
            try {
              innerSendAborted(reason, description);
              scheduleTimeoutAfterBlockSends();
            } catch (NotConnectedException e) {
              onDisconnect();
            }
          }
         
        };
      }
    }
    if(blockSendsPending != 0) {
      if(logMINOR) Logger.minor(this, "maybeFail() waiting for "+blockSendsPending+" block sends on "+this);
      if(_sentSendAborted)
        return nullFuture; // Wait for blockSendsPending to reach 0
      else {
        _sentSendAborted = true;
        // They have sent us a cancel, but we still need to send them an ack or they will do a fatal timeout.
        return new Future() {

          @Override
          public void execute() {
            try {
              innerSendAborted(reason, description);
            } catch (NotConnectedException e) {
              onDisconnect();
            }
          }
       
        };
      }
    }
    if(logMINOR) Logger.minor(this, "maybeFail() completing on "+this);
    _completed = true;
    decRunningBlockTransmits();
    final boolean sendAborted = _sentSendAborted;
    _sentSendAborted = true;
    return new Future() {

      @Override
      public void execute() {
        if(!sendAborted) {
          try {
            innerSendAborted(reason, description);
          } catch (NotConnectedException e) {
            onDisconnect();
          }
        }
        callCallback(false);
      }
     
    };
  }
 
  /** Abort the send, and then send the sendAborted message. Don't do anything if the
   * send has already been aborted. */
  public void abortSend(int reason, String desc) throws NotConnectedException {
    if(logMINOR) Logger.minor(this, "Aborting send on "+this);
    Future fail;
    synchronized(_senderThread) {
      _failed = true;
      fail = maybeFail(reason, desc);
    }
    fail.execute();
    cancelItemsPending();
  }
 
  public void innerSendAborted(int reason, String desc) throws NotConnectedException {
    _usm.send(_destination, DMT.createSendAborted(_uid, reason, desc), _ctr);
  }
 
  private void sendAllSentNotification() {
    try {
      _usm.send(_destination, DMT.createAllSent(_uid, realTime), _ctr);
    } catch (NotConnectedException e) {
      Logger.normal(this, "disconnected for allSent()");
    }
  }
 
  public interface ReceiverAbortHandler {
   
    /** @return True to cancel the PRB and thus cascade the cancel to the downstream
     * transfer, false otherwise. */
    public boolean onAbort();
   
  }
 
  public static final ReceiverAbortHandler ALWAYS_CASCADE = new ReceiverAbortHandler() {

    @Override
    public boolean onAbort() {
      return true;
    }
   
  };
 
  public static final ReceiverAbortHandler NEVER_CASCADE = new ReceiverAbortHandler() {

    @Override
    public boolean onAbort() {
      return false;
    }
   
  };
 
  public interface BlockTransmitterCompletion {
   
    public void blockTransferFinished(boolean success);
   
  }
 
  private PartiallyReceivedBlock.PacketReceivedListener myListener = null;

  private AsyncMessageFilterCallback cbAllReceived = new SlowAsyncMessageFilterCallback() {

    @Override
    public void onMatched(Message m) {
      if(logMINOR) {
        long endTime = System.currentTimeMillis();
        long transferTime = (endTime - startTime);
        synchronized(avgTimeTaken) {
          avgTimeTaken.report(transferTime);
          Logger.minor(this, "Block send took "+transferTime+" : "+avgTimeTaken+" on "+BlockTransmitter.this);
        }
      }
      synchronized(_senderThread) {
        _receivedSendCompletion = true;
        _receivedSendSuccess = true;
        if(!maybeAllSent()) return;
        if(!maybeComplete()) return;
      }
      callCallback(true);
    }

    @Override
    public boolean shouldTimeout() {
      synchronized(_senderThread) {
        // We are waiting for the send completion, which is set on timeout as well as on receiving a message.
        // In some corner cases we might want to get the allReceived after setting _failed, so don't timeout on _failed.
        // We do want to timeout on _completed because that means everything is finished - it is only set in maybeComplete() and maybeFail().
        if(_receivedSendCompletion || _completed) return true;
      }
      return false;
    }

    @Override
    public void onTimeout() {
      // Do nothing
    }

    @Override
    public void onDisconnect(PeerContext ctx) {
      BlockTransmitter.this.onDisconnect();
    }

    @Override
    public void onRestarted(PeerContext ctx) {
      BlockTransmitter.this.onDisconnect();
    }

    @Override
    public int getPriority() {
      return NativeThread.NORM_PRIORITY;
    }
   
  };
 
  private AsyncMessageFilterCallback cbSendAborted = new SlowAsyncMessageFilterCallback() {

    @Override
    public void onMatched(Message msg) {
      if((!_prb.isAborted()) && abortHandler.onAbort())
        _prb.abort(RetrievalException.CANCELLED_BY_RECEIVER, "Cascading cancel from receiver", true);
      Future fail;
      synchronized(_senderThread) {
        _receivedSendCompletion = true;
        _receivedSendSuccess = false;
        fail = maybeFail(msg.getInt(DMT.REASON), msg.getString(DMT.DESCRIPTION));
        if(logMINOR) Logger.minor(this, "Transfer got sendAborted on "+BlockTransmitter.this);
      }
      fail.execute();
      cancelItemsPending();
    }

    @Override
    public boolean shouldTimeout() {
      synchronized(_senderThread) {
        // We are waiting for the send completion, which is set on timeout as well as on receiving a message.
        // We don't want to timeout on _failed because we can set _failed, send sendAborted, and then wait for the acknowledging sendAborted.
        // We do want to timeout on _completed because that means everything is finished - it is only set in maybeComplete() and maybeFail().
        if(_receivedSendCompletion || _completed) return true;
      }
      return false;
    }

    @Override
    public void onTimeout() {
      // Do nothing
    }

    @Override
    public void onDisconnect(PeerContext ctx) {
      BlockTransmitter.this.onDisconnect();
    }

    @Override
    public void onRestarted(PeerContext ctx) {
      BlockTransmitter.this.onDisconnect();
    }

    @Override
    public int getPriority() {
      return NativeThread.NORM_PRIORITY;
    }
   
  };
 
  private void onDisconnect() {
    Logger.normal(this, "Terminating send "+_uid+" to "+_destination+" from "+_destination.getSocketHandler()+" because node disconnected while waiting");
    //They disconnected, can't send an abort to them then can we?
    Future fail;
    synchronized(_senderThread) {
      _receivedSendCompletion = true; // effectively
      blockSendsPending = 0; // effectively
      _sentSendAborted = true; // effectively
      fail = maybeFail(RetrievalException.SENDER_DISCONNECTED, "Sender disconnected");
    }
    fail.execute();
    // Sometimes disconnect doesn't clear the message queue.
    // Since we are cancelling the transfer, we need to unqueue the messages.
    cancelItemsPending();
  }
 
  private void onAborted(int reason, String description) {
    if(logMINOR) Logger.minor(this, "Aborting on "+this);
    Future fail;
    synchronized(_senderThread) {
      timeAllSent = -1;
      _failed = true;
      _senderThread.notifyAll();
      fail = maybeFail(reason, description);
    }
    fail.execute();
    cancelItemsPending();
  }

  private long startTime;
 
  /** Send the data, off-thread. */
  public void sendAsync() {
    startTime = System.currentTimeMillis();
   
    if(logMINOR) Logger.minor(this, "Starting async send on "+this);
    incRunningBlockTransmits();
   
    try {
      synchronized(_prb) {
        _unsent = _prb.addListener(myListener = new PartiallyReceivedBlock.PacketReceivedListener() {;

          @Override
          public void packetReceived(int packetNo) {
            synchronized(_senderThread) {
              if(logMINOR) Logger.minor(this, "Got packet "+packetNo+" for "+_uid+" to "+_destination);
              if(_unsent.contains(packetNo)) {
                Logger.error(this, "Already in unsent: "+packetNo+" for "+this+" unsent is "+_unsent, new Exception("error"));
                return;
              }
              if(_sentPackets.bitAt(packetNo)) {
                Logger.error(this, "Already sent packet in packetReceived: "+packetNo+" for "+this+" unsent is "+_unsent+" sent is "+_sentPackets, new Exception("error"));
                return;
              }
              _unsent.addLast(packetNo);
              timeAllSent = -1;
              _senderThread.schedule();
            }
          }

          @Override
          public void receiveAborted(int reason, String description) {
            onAborted(reason, description);
          }
        });
      }
      _senderThread.schedule();

      MessageFilter mfAllReceived = MessageFilter.create().setType(DMT.allReceived).setField(DMT.UID, _uid).setSource(_destination).setNoTimeout();
      MessageFilter mfSendAborted = MessageFilter.create().setType(DMT.sendAborted).setField(DMT.UID, _uid).setSource(_destination).setNoTimeout();
     
      try {
        _usm.addAsyncFilter(mfAllReceived, cbAllReceived, _ctr);
        _usm.addAsyncFilter(mfSendAborted, cbSendAborted, _ctr);
      } catch (DisconnectedException e) {
        onDisconnect();
      }
     
    } catch (AbortedException e) {
      onAborted(_prb._abortReason, _prb._abortDescription);
    }
  }
 
  private void cancelItemsPending() {
    MessageItem[] items;
    synchronized(itemsPending) {
      items = itemsPending.toArray(new MessageItem[itemsPending.size()]);
      itemsPending.clear();
    }
    for(MessageItem item : items) {
      if(!_destination.unqueueMessage(item)) {
        // Race condition, can happen
        if(logMINOR) Logger.minor(this, "Message not queued ?!?!?!? on "+this+" : "+item);
      }
    }
  }

  long timeLastBlockSendCompleted = -1;
 
  private static synchronized void incRunningBlockTransmits() {
    runningBlockTransmits++;
    if(logMINOR) Logger.minor(BlockTransmitter.class, "Started a block transmit, running: "+runningBlockTransmits);
  }

  private static synchronized void decRunningBlockTransmits() {
    runningBlockTransmits--;
    if(logMINOR) Logger.minor(BlockTransmitter.class, "Finished a block transmit, running: "+runningBlockTransmits);
  }

  private void cleanup() {
    // FIXME remove filters
    // shouldTimeout() should deal with them adequately, maybe we don't need to explicitly remove them.
    if (myListener!=null)
      _prb.removeListener(myListener);
  }

  private class MyAsyncMessageCallback implements AsyncMessageCallback {

   
    MyAsyncMessageCallback() {
      synchronized(_senderThread) {
        blockSendsPending++;
      }
    }
   
    private boolean completed = false;
   
    @Override
    public void sent() {
      if(logMINOR) Logger.minor(this, "Sent block on "+BlockTransmitter.this);
      // Wait for acknowledged
    }

    @Override
    public void acknowledged() {
      complete(false);
    }

    @Override
    public void disconnected() {
      // FIXME kill transfer
      complete(true);
    }

    @Override
    public void fatalError() {
      // FIXME kill transfer
      complete(true);
    }
   
    private void complete(boolean failed) {
      if(logMINOR) Logger.minor(this, "Completed send on a block for "+BlockTransmitter.this);
      boolean success = false;
      long now = System.currentTimeMillis();
      boolean callCallback = false;
      long delta = -1;
      synchronized(_senderThread) {
        if(completed) return;
        completed = true;
        if(lastSentPacket > 0) {
          delta = now - lastSentPacket;
          long threshold = (realTime ? BlockReceiver.RECEIPT_TIMEOUT_REALTIME : BlockReceiver.RECEIPT_TIMEOUT_BULK);
          if(delta > threshold)
            Logger.warning(this, "Time between packets on "+BlockTransmitter.this+" : "+TimeUtil.formatTime(delta, 2, true)+" ( "+delta+"ms) realtime="+realTime);
          else if(delta > threshold / 5)
            Logger.normal(this, "Time between packets on "+BlockTransmitter.this+" : "+TimeUtil.formatTime(delta, 2, true)+" ( "+delta+"ms) realtime="+realTime);
          else if(logMINOR)
            Logger.minor(this, "Time between packets on "+BlockTransmitter.this+" : "+TimeUtil.formatTime(delta, 2, true)+" ( "+delta+"ms) realtime="+realTime);
        }
        lastSentPacket = now;
        blockSendsPending--;
        if(logMINOR) Logger.minor(this, "Pending: "+blockSendsPending);
        if(maybeAllSent()) {
          if(maybeComplete()) {
            callCallback = true;
            success = _receivedSendSuccess;
          }
        }
      }
      if(!failed)
        // Everything is throttled, but payload is not reported.
        _ctr.sentPayload(PACKET_SIZE);
      if(callCallback) {
        callCallback(success);
      }
      if(delta > 0 && blockTimeCallback != null) {
        blockTimeCallback.blockTime(delta, realTime);
      }
    }

  };
 
  private int blockSendsPending = 0;
 
  private long lastSentPacket = -1;
 
  private static MedianMeanRunningAverage avgTimeTaken = new MedianMeanRunningAverage();
 
  /** LOCKING: Must be called with _senderThread held. */
  private int getNumSent() {
    int ret = 0;
    for (int x=0; x<_sentPackets.getSize(); x++) {
      if (_sentPackets.bitAt(x)) {
        ret++;
      }
    }
    return ret;
  }
 
  public void callCallback(final boolean success) {
    if(_callback != null) {
      _executor.execute(new Runnable() {

        @Override
        public void run() {
          try {
            _callback.blockTransferFinished(success);
          } finally {
            cleanup();
          }
        }
       
      }, "BlockTransmitter completion callback for "+this);
    } else {
      cleanup();
    }
  }

  public PeerContext getDestination() {
    return _destination;
  }
 
  @Override
  public String toString() {
    return "BlockTransmitter for "+_uid+" to "+_destination.shortToString();
  }

  public synchronized static int getRunningSends() {
    return runningBlockTransmits;
  }
}
TOP

Related Classes of freenet.io.xfer.BlockTransmitter$Future

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.