package net.i2p.router.transport.ntcp;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Pool of running threads which will transform the next I2NP message into
* something ready to be transferred over an NTCP connection, including the
* encryption of the data read.
*
*/
class Writer {
private final Log _log;
private final Set<NTCPConnection> _pendingConnections;
private final Set<NTCPConnection> _liveWrites;
private final Set<NTCPConnection> _writeAfterLive;
private final List<Runner> _runners;
public Writer(RouterContext ctx) {
_log = ctx.logManager().getLog(getClass());
_pendingConnections = new LinkedHashSet<NTCPConnection>(16);
_runners = new ArrayList<Runner>(5);
_liveWrites = new HashSet<NTCPConnection>(5);
_writeAfterLive = new HashSet<NTCPConnection>(5);
}
public synchronized void startWriting(int numWriters) {
for (int i = 1; i <=numWriters; i++) {
Runner r = new Runner();
I2PThread t = new I2PThread(r, "NTCP writer " + i + '/' + numWriters, true);
_runners.add(r);
t.start();
}
}
public synchronized void stopWriting() {
while (!_runners.isEmpty()) {
Runner r = _runners.remove(0);
r.stop();
}
synchronized (_pendingConnections) {
_writeAfterLive.clear();
_pendingConnections.notifyAll();
}
}
public void wantsWrite(NTCPConnection con, String source) {
//if (con.getCurrentOutbound() != null)
// throw new RuntimeException("Current outbound message already in play on " + con);
boolean already = false;
boolean pending = false;
synchronized (_pendingConnections) {
if (_liveWrites.contains(con)) {
_writeAfterLive.add(con);
already = true;
} else {
pending = _pendingConnections.add(con);
// only notify here if added?
}
_pendingConnections.notify();
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("wantsWrite: " + con + " already live? " + already + " added to pending? " + pending + ": " + source);
}
public void connectionClosed(NTCPConnection con) {
synchronized (_pendingConnections) {
_writeAfterLive.remove(con);
_pendingConnections.remove(con);
// necessary?
_pendingConnections.notify();
}
}
private class Runner implements Runnable {
/** a scratch space to serialize and encrypt messages */
private final NTCPConnection.PrepBuffer _prepBuffer;
private volatile boolean _stop;
public Runner() {
_prepBuffer = new NTCPConnection.PrepBuffer();
}
public void stop() { _stop = true; }
public void run() {
if (_log.shouldLog(Log.INFO)) _log.info("Starting writer");
NTCPConnection con = null;
while (!_stop) {
try {
synchronized (_pendingConnections) {
boolean keepWriting = (con != null) && _writeAfterLive.remove(con);
if (keepWriting) {
// keep on writing the same one
if (_log.shouldLog(Log.DEBUG))
_log.debug("Keep writing on the same connection: " + con);
} else {
_liveWrites.remove(con);
con = null;
if (_pendingConnections.isEmpty()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Done writing, but nothing pending, so wait");
_pendingConnections.wait();
} else {
Iterator<NTCPConnection> iter = _pendingConnections.iterator();
con = iter.next();
iter.remove();
_liveWrites.add(con);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Switch to writing on: " + con);
}
}
}
} catch (InterruptedException ie) {}
if (!_stop && (con != null)) {
try {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Prepare next write on: " + con);
_prepBuffer.init();
con.prepareNextWrite(_prepBuffer);
} catch (RuntimeException re) {
_log.log(Log.CRIT, "Error in the ntcp writer on " + con, re);
}
}
}
if (_log.shouldLog(Log.INFO)) _log.info("Stopping writer");
}
}
}