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); } }