/* 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.clients.fcp; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayDeque; import java.util.Deque; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; public class FCPConnectionOutputHandler implements Runnable { final FCPConnectionHandler handler; final Deque<FCPMessage> outQueue; // Synced on outQueue private boolean closedOutputQueue; private static volatile boolean logMINOR; private static volatile boolean logDEBUG; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this); } }); } public FCPConnectionOutputHandler(FCPConnectionHandler handler) { this.handler = handler; this.outQueue = new ArrayDeque<FCPMessage>(); } void start() { if (handler.sock == null) return; handler.server.node.executor.execute(this, "FCP output handler for "+handler.sock.getRemoteSocketAddress()+ ':' +handler.sock.getPort()); } @Override public void run() { freenet.support.Logger.OSThread.logPID(this); try { realRun(); } catch (IOException e) { if(logMINOR) Logger.minor(this, "Caught "+e, e); } catch (Throwable t) { Logger.error(this, "Caught "+t, t); } finally { // Set the closed flag so that onClosed(), both on this thread and the input thread, doesn't wait forever. // This happens in realRun() on a healthy exit, but we must do it here too to handle an exceptional exit. // I.e. the other side closed the connection, and we threw an IOException. synchronized(outQueue) { closedOutputQueue = true; } } handler.close(); handler.closedOutput(); } private void realRun() throws IOException { OutputStream os = new BufferedOutputStream(handler.sock.getOutputStream(), 4096); while(true) { boolean closed; FCPMessage msg = null; boolean flushed = false; while(true) { closed = handler.isClosed(); boolean shouldFlush = false; synchronized(outQueue) { if(outQueue.isEmpty()) { if(closed) { closedOutputQueue = true; outQueue.notifyAll(); break; } if(!flushed) shouldFlush = true; else { try { outQueue.wait(1000); } catch (InterruptedException e) { // Ignore } continue; } } else { msg = outQueue.removeFirst(); } } if(shouldFlush) { if(logMINOR) Logger.minor(this, "Flushing"); os.flush(); flushed = true; continue; } else { break; } } if(msg == null) { if(closed) { os.flush(); os.close(); return; } } else { if(logMINOR) Logger.minor(this, "Sending "+msg); msg.send(os); flushed = false; } } } /** * @deprecated * Use {@link FCPConnectionHandler#send(FCPMessage)} instead of using public access to the * member variable {@link FCPConnectionHandler#outputHandler} to call this function here * upon the outputHandler. In other words: Replace * <code>fcpConnectionHandler.outputHandler.queue(...)</code> * with <code>fcpConnectionHandler.send(...)</code><br> * TODO: The deprecation is merely to enforce people to stop using the said member variable * in a public way. The function itself is fine to stay. Once the public usage has been * replaced by the suggested way of using send(), please make the member variable * {@link FCPConnectionHandler#outputHandler} private and remove the deprecation at this * function here. */ @Deprecated public void queue(FCPMessage msg) { if(logDEBUG) Logger.debug(this, "Queueing "+msg, new Exception("debug")); if(msg == null) throw new NullPointerException(); boolean neverDropAMessage = handler.server.neverDropAMessage(); int MAX_QUEUE_LENGTH = handler.server.maxMessageQueueLength(); synchronized(outQueue) { if(closedOutputQueue) { Logger.error(this, "Closed already: "+this+" queueing message "+msg); // FIXME throw something??? return; } if(outQueue.size() >= MAX_QUEUE_LENGTH) { if(neverDropAMessage) { Logger.error(this, "FCP message queue length is "+outQueue.size()+" for "+handler+" - not dropping message as configured..."); } else { Logger.error(this, "Dropping FCP message to "+handler+" : "+outQueue.size()+" messages queued - maybe client died?", new Exception("debug")); return; } } outQueue.add(msg); outQueue.notifyAll(); } } public void onClosed() { synchronized(outQueue) { outQueue.notifyAll(); // Give a chance to the output handler to flush // its queue before the socket is closed // @see #2019 - nextgens while(!outQueue.isEmpty()) { if(closedOutputQueue) return; try { outQueue.wait(1500); } catch (InterruptedException e) { continue; } } } } public boolean isQueueHalfFull() { int MAX_QUEUE_LENGTH = handler.server.maxMessageQueueLength(); synchronized(outQueue) { return outQueue.size() > MAX_QUEUE_LENGTH / 2; } } }