package freenet.node;
import java.lang.ref.WeakReference;
import freenet.keys.NodeCHK;
import freenet.support.Logger;
import freenet.support.TimeUtil;
/**
* Tag for a request.
*
* @author Matthew Toseland <toad@amphibian.dyndns.org> (0xE43DA450)
*/
public class RequestTag extends UIDTag {
private static volatile boolean logMINOR;
static {
Logger.registerClass(RequestTag.class);
}
enum START {
ASYNC_GET,
LOCAL,
REMOTE
}
final START start;
final boolean isSSK;
private boolean servedFromDatastore;
private WeakReference<RequestSender> sender;
private boolean sent;
private int requestSenderFinishedCode = RequestSender.NOT_FINISHED;
private Throwable handlerThrew;
private boolean rejected;
private boolean abortedDownstreamTransfer;
private int abortedDownstreamReason;
private String abortedDownstreamDesc;
private boolean handlerDisconnected;
private WeakReference<PeerNode> waitingForOpennet;
private boolean handlerTransferring;
private boolean senderTransferring;
/** Set if transferring */
private NodeCHK key;
public RequestTag(boolean isSSK, START start, PeerNode source, boolean realTimeFlag, long uid, Node node) {
super(source, realTimeFlag, uid, node);
this.start = start;
this.isSSK = isSSK;
}
public void setRequestSenderFinished(int status) {
boolean noRecordUnlock;
synchronized(this) {
if(status == RequestSender.NOT_FINISHED) throw new IllegalArgumentException();
requestSenderFinishedCode = status;
if(!mustUnlock()) return;
noRecordUnlock = this.noRecordUnlock;
}
innerUnlock(noRecordUnlock);
}
public synchronized void setSender(RequestSender rs, boolean coalesced) {
// If it's because of transfer coalescing, we won't get anything from the RequestSender, so we should not wait for it.
if(!coalesced)
sent = true;
sender = new WeakReference<RequestSender>(rs);
}
@Override
protected synchronized boolean mustUnlock() {
if(sent && requestSenderFinishedCode == RequestSender.NOT_FINISHED) return false;
if(waitingForOpennet != null && waitingForOpennet.get() != null) return false;
return super.mustUnlock();
}
@Override
protected final void innerUnlock(boolean noRecordUnlock) {
boolean handlerFinished;
boolean senderFinished;
NodeCHK k = null;
RequestSender s = null;
synchronized(this) {
handlerFinished = this.handlerTransferring;
handlerTransferring = false;
senderFinished = this.senderTransferring;
if(senderFinished) {
Logger.error(this, "Nobody called senderTransferEnds() for "+this, new Exception("debug"));
k = key;
s = sender.get();
}
senderTransferring = false;
}
super.innerUnlock(noRecordUnlock);
if(handlerFinished)
tracker.removeTransferringRequestHandler(uid);
if(senderFinished) {
assert(k != null);
assert(s != null);
tracker.removeTransferringSender(k, s);
}
}
/** The handler threw a Throwable, i.e. failed due to a serious bug.
* Unlock the handler, and record the throwable in case of future problems.
* @param t The Throwable thrown by the RequestHandler. */
public void handlerThrew(Throwable t) {
synchronized(this) {
this.handlerThrew = t;
}
unlockHandler(); // FIXME optimise synchronization in a clean way.
}
public synchronized void setServedFromDatastore() {
servedFromDatastore = true;
}
public synchronized 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());
}
}
if(sent)
sb.append(" sent");
sb.append(" finishedCode=").append(requestSenderFinishedCode);
sb.append(" rejected=").append(rejected);
if(handlerThrew != null)
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(waitingForOpennet != null) {
PeerNode pn = waitingForOpennet.get();
sb.append(" waitingForOpennet=");
sb.append(pn == null ? "(null)" : pn.shortToString());
}
sb.append(" : ");
sb.append(super.toString());
if(handlerThrew != null)
Logger.error(this, sb.toString(), handlerThrew);
else
Logger.error(this, sb.toString());
}
public synchronized void onAbortDownstreamTransfers(int reason, String desc) {
abortedDownstreamTransfer = true;
abortedDownstreamReason = reason;
abortedDownstreamDesc = desc;
}
public synchronized void handlerDisconnected() {
handlerDisconnected = true;
}
@Override
public synchronized int expectedTransfersIn(boolean ignoreLocalVsRemote,
int outwardTransfersPerInsert, boolean forAccept) {
if(!accepted) return 0;
return notRoutedOnwards ? 0 : 1;
}
@Override
public synchronized int expectedTransfersOut(boolean ignoreLocalVsRemote,
int outwardTransfersPerInsert, boolean forAccept) {
if(!accepted) return 0;
if(completedDownstreamTransfers) return 0;
if(forAccept && (sourceRestarted || unlockedHandler)) return 0;
return ((!isLocal()) || ignoreLocalVsRemote) ? 1 : 0;
}
private boolean completedDownstreamTransfers;
public synchronized void completedDownstreamTransfers() {
this.completedDownstreamTransfers = true;
}
@Override
public boolean isSSK() {
return isSSK;
}
@Override
public boolean isInsert() {
return false;
}
@Override
public boolean isOfferReply() {
return false;
}
public synchronized void waitingForOpennet(PeerNode next) {
if(waitingForOpennet != null)
Logger.error(this, "Have already waited for opennet: "+waitingForOpennet.get()+" on "+this, new Exception("error"));
this.waitingForOpennet = next.myRef;
}
public void finishedWaitingForOpennet(PeerNode next) {
boolean noRecordUnlock;
synchronized(this) {
if(waitingForOpennet == null) {
if(logMINOR) Logger.minor(this, "Not waiting for opennet!");
return;
}
PeerNode got = waitingForOpennet.get();
if(got != next) {
Logger.error(this, "Finished waiting for opennet on "+next+" but was waiting for "+got);
}
waitingForOpennet = null;
if(!mustUnlock()) return;
noRecordUnlock = this.noRecordUnlock;
}
innerUnlock(noRecordUnlock);
}
@Override
public synchronized boolean currentlyRoutingTo(PeerNode peer) {
if(waitingForOpennet != null && waitingForOpennet == peer.myRef)
return true;
return super.currentlyRoutingTo(peer);
}
public void handlerTransferBegins() {
synchronized(this) {
if(handlerTransferring) return;
handlerTransferring = true;
}
tracker.addTransferringRequestHandler(uid);
}
public void senderTransferBegins(NodeCHK k, RequestSender requestSender) {
synchronized(this) {
if(senderTransferring) return;
senderTransferring = true;
if(this.sender == null || this.sender.get() != requestSender)
throw new IllegalStateException("Set RequestSender first!");
this.key = k;
}
tracker.addTransferringSender(k, requestSender);
}
public void senderTransferEnds(NodeCHK key, RequestSender requestSender) {
synchronized(this) {
if(!senderTransferring)
// Already unlocked. This is okay.
return;
senderTransferring = false;
assert(this.sender != null && this.sender.get() == requestSender);
assert(this.key != null && this.key.equals(key));
this.key = null;
}
tracker.removeTransferringSender(key, requestSender);
}
}