// Don't fire transfer begins on a fork since we have not set headers or prb.
// If we find the data we will offer it to the requester.
fireCHKTransferBegins();
final long tStart = System.currentTimeMillis();
final BlockReceiver br = new BlockReceiver(node.usm, next, uid, prb, this, node.getTicker(), true, realTimeFlag, myTimeoutHandler, true);
if(failNow) {
if(logMINOR) Logger.minor(this, "Terminating forked transfer on "+this+" from "+next);
prb.abort(RetrievalException.CANCELLED_BY_RECEIVER, "Cancelling fork", true);
br.receive(new BlockReceiverCompletion() {
@Override
public void blockReceived(byte[] buf) {
if(!wasFork)
origTag.senderTransferEnds((NodeCHK)key, RequestSender.this);
next.noLongerRoutingTo(origTag, false);
}
@Override
public void blockReceiveFailed(RetrievalException e) {
if(!wasFork)
origTag.senderTransferEnds((NodeCHK)key, RequestSender.this);
next.noLongerRoutingTo(origTag, false);
}
});
return;
}
if(logMINOR) Logger.minor(this, "Receiving data");
if(!wasFork) {
synchronized(this) {
transferringFrom = next;
}
} else
if(logMINOR) Logger.minor(this, "Receiving data from fork");
receivingAsync = true;
br.receive(new BlockReceiverCompletion() {
@Override
public void blockReceived(byte[] data) {
try {
long tEnd = System.currentTimeMillis();
transferTime = tEnd - tStart;
boolean haveSetPRB = false;
synchronized(RequestSender.this) {
transferringFrom = null;
if(RequestSender.this.prb == null || !RequestSender.this.prb.allReceivedAndNotAborted()) {
RequestSender.this.prb = prb;
haveSetPRB = true;
}
}
if(!wasFork)
origTag.senderTransferEnds((NodeCHK)key, RequestSender.this);
next.transferSuccess(realTimeFlag);
next.successNotOverload(realTimeFlag);
node.nodeStats.successfulBlockReceive(realTimeFlag, source == null);
if(logMINOR) Logger.minor(this, "Received data");
// Received data
try {
verifyAndCommit(waiter.headers, data);
if(logMINOR) Logger.minor(this, "Written to store");
} catch (KeyVerifyException e1) {
Logger.normal(this, "Got data but verify failed: "+e1, e1);
node.failureTable.onFinalFailure(key, next, htl, origHTL, FailureTable.RECENTLY_FAILED_TIME, FailureTable.REJECT_TIME, source);
if(!wasFork)
finish(VERIFY_FAILURE, next, false);
else
next.noLongerRoutingTo(origTag, false);
return;
}
if(haveSetPRB) // It was a fork, so we didn't immediately send the data.
fireCHKTransferBegins();
finish(SUCCESS, next, false);
} catch (Throwable t) {
Logger.error(this, "Failed on "+this, t);
if(!wasFork)
finish(INTERNAL_ERROR, next, true);
} finally {
if(wasFork)
next.noLongerRoutingTo(origTag, false);
}
}
@Override
public void blockReceiveFailed(
RetrievalException e) {
try {
synchronized(RequestSender.this) {
transferringFrom = null;
}
origTag.senderTransferEnds((NodeCHK)key, RequestSender.this);
if (e.getReason()==RetrievalException.SENDER_DISCONNECTED)
Logger.normal(this, "Transfer failed (disconnect): "+e, e);
else
// 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 failed ("+e.getReason()+"/"+RetrievalException.getErrString(e.getReason())+"): "+e+" from "+next, e);
if(RequestSender.this.source == null)
Logger.normal(this, "Local transfer failed: "+e.getReason()+" : "+RetrievalException.getErrString(e.getReason())+"): "+e+" from "+next, e);
// We do an ordinary backoff in all cases.
if(!prb.abortedLocally())
next.localRejectedOverload("TransferFailedRequest"+e.getReason(), realTimeFlag);
node.failureTable.onFinalFailure(key, next, htl, origHTL, FailureTable.RECENTLY_FAILED_TIME, FailureTable.REJECT_TIME, source);
if(!wasFork)
finish(TRANSFER_FAILED, next, false);
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);
// But we only do a transfer backoff (which is separate, and starts at a higher threshold) if we timed out.
if(timeout) {
// Looks like a timeout. Backoff.