package net.i2p.client.impl; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import net.i2p.I2PAppContext; import net.i2p.client.I2PSessionException; import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessageException; import net.i2p.internal.PoisonI2CPMessage; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; /** * Copied from net.i2p.router.client * We need a single thread that writes so we don't have issues with * the Piped Streams used in InternalSocket. * * @author zzz from net.i2p.router.client.ClientWriterRunner */ class ClientWriterRunner implements Runnable { private final OutputStream _out; private final I2PSessionImpl _session; private final BlockingQueue<I2CPMessage> _messagesToWrite; private static final AtomicLong __Id = new AtomicLong(); //private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ClientWriterRunner.class); private static final int MAX_QUEUE_SIZE = 32; private static final long MAX_SEND_WAIT = 10*1000; /** * As of 0.9.11 does not start the thread, caller must call startWriting() */ public ClientWriterRunner(OutputStream out, I2PSessionImpl session) { _out = new BufferedOutputStream(out); _session = session; _messagesToWrite = new LinkedBlockingQueue<I2CPMessage>(MAX_QUEUE_SIZE); } /** * @since 0.9.11 */ public void startWriting() { Thread t = new I2PAppThread(this, "I2CP Client Writer " + __Id.incrementAndGet(), true); t.start(); } /** * Add this message to the writer's queue. * Blocking if queue is full. * @throws I2PSessionException if we wait too long or are interrupted */ public void addMessage(I2CPMessage msg) throws I2PSessionException { try { if (!_messagesToWrite.offer(msg, MAX_SEND_WAIT, TimeUnit.MILLISECONDS)) throw new I2PSessionException("Timed out waiting while write queue was full"); } catch (InterruptedException ie) { throw new I2PSessionException("Interrupted while write queue was full", ie); } } /** * No more messages - dont even try to send what we have * */ public void stopWriting() { _messagesToWrite.clear(); try { _messagesToWrite.put(new PoisonI2CPMessage()); } catch (InterruptedException ie) {} } public void run() { I2CPMessage msg; while (!_session.isClosed()) { try { msg = _messagesToWrite.take(); } catch (InterruptedException ie) { continue; } if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE) break; // only thread, we don't need synchronized try { msg.writeMessage(_out); if (_messagesToWrite.isEmpty()) _out.flush(); } catch (I2CPMessageException ime) { _session.propogateError("Error writing out the message", ime); _session.disconnect(); break; } catch (IOException ioe) { _session.propogateError("Error writing out the message", ioe); _session.disconnect(); break; } } _messagesToWrite.clear(); } }